1 Getting started

To copy the code, click the button in the upper right corner of the code-chunks.

1.1 clean up

rm(list = ls())
gc()


1.2 general custom functions

  • fpackage.check: Check if packages are installed (and install if not) in R
  • fsave: Function to save data with time stamp in correct directory
  • fload: Load R-objects under new names
  • fshowdf: Print objects (tibble / data.frame) nicely on screen in .Rmd.
  • fplot_graph: visualize the network topology and agent positions (degree-trait correlation)
fpackage.check <- function(packages) {
    lapply(packages, FUN = function(x) {
        if (!require(x, character.only = TRUE)) {
            install.packages(x, dependencies = TRUE)
            library(x, character.only = TRUE)
        }
    })
}

fsave <- function(x, file, location = "./data/processed/", ...) {
    if (!dir.exists(location))
        dir.create(location)
    datename <- substr(gsub("[:-]", "", Sys.time()), 1, 8)
    totalname <- paste(location, datename, file, sep = "")
    print(paste("SAVED: ", totalname, sep = ""))
    save(x, file = totalname)
}

fload <- function(fileName) {
    load(fileName)
    get(ls()[ls() != "fileName"])
}

fshowdf <- function(x, ...) {
    knitr::kable(x, digits = 2, "html", ...) %>%
        kableExtra::kable_styling(bootstrap_options = c("striped", "hover")) %>%
        kableExtra::scroll_box(width = "100%", height = "300px")
}

fplot_graph <- function(graph, main=NULL, layout_algo=NULL, 
                        col1 = "#800080", col2 =  "#FFD700", legend=TRUE) {
  plot(graph,
       main = main,
       layout = layout_algo,
       vertex.label = NA,
       vertex.size = degree(graph)*.5 + 3, # node size based on degree
       vertex.color = ifelse(V(graph)$role == "trendsetter", col1, col2),
       edge.width = 0.5,
       
       edge.color = "darkgrey")
  #add legend
  if(legend==TRUE) {
    legend("bottomleft",
         legend = c("Trendsetter", "Conformist"),
         pch = 21,
         col = c(col1,col2),
         pt.bg = c(col1,col2),
         pt.cex = 3,
         bty = "n")
  }
}


1.3 necessary packages

packages = c("tidyverse", "igraph", "vioplot", "ggplot2", "ggpubr", "plotly", "parallel", "foreach",
    "doParallel", "ggh4x", "grid", "gridExtra", "patchwork", "ggplotify")
invisible(fpackage.check(packages))
rm(packages)

2 utility function

A function to calculate the payoff of a specific choice, for an agent (id), given the agents’ current statuses, the network structure, and the utility function parameters:

futility <- function(agent_id, choice, agents, network, params) {
  # get ego and his local neighborhood
  ego <- agents[agent_id, ]
  neighbors <- neighbors(network, ego$id)
  alters <- agents[as.numeric(neighbors), ]
  n <- nrow(alters)

  # proportion of neighbors who adopted (p) or resisted (1-p) the trend
  p <- sum(alters$choice == 1) / n
  q <- 1 - p  # proportion of trend-resistant neighbors
  
  # calculate diminishing returns function
  diminishing_returns <- function(x, v, lambda) {
    v * (1 - exp(-lambda * x)) / (1 - exp(-lambda))
  }
  
  # calculate expected utility (depending on alters' choices in prior round)
  if(ego$role == "conformist") {
    if (choice == 0) { # resist the trend
      choice_payoff <- params$s # fixed utility for resisting
      coordination_payoff <- diminishing_returns(q, params$w, params$lambda2)
    } else { #follow the trend
      choice_payoff <- 0
      coordination_payoff <- diminishing_returns(p, params$z, params$lambda1)
    }
  } else { # trendsetters only care about following
    if (choice == 1) {
      choice_payoff <- params$e
    } else {
      choice_payoff <- 0
    }
    coordination_payoff <- 0
  }
  
  # return total utility and counts of neighbors
  return(list(utility = choice_payoff + coordination_payoff,
              n1 = sum(alters$choice == 1),  # count of trend-adopting neighbors
              n0 = sum(alters$choice == 0))) # count of trend-resistant neighbors
}

3 network structure

3.1 configuration model

A function to generate a degree sequence based on a power-law / log-normal distribution:

fdegseq <- function(n, dist = "power-law", alpha, k_min = 1, k_max = n - 1, seed = NULL) {

    if (!is.null(seed)) {
        set.seed(seed)
    }

    # generate a degree sequence based on a power-law distribution of the form p(k) ∝ k^{-alpha}

    # create probability distribution
    probs <- (1/(k_min:k_max))^alpha

    # normalize probabilities
    probs <- probs/sum(probs)

    # sample a degree sequence
    degseq <- sample(k_min:k_max, size = n, replace = TRUE, prob = (1/(k_min:k_max))^alpha)

    # correct the degree sequence if its sum is odd (necessary for the configuration model)
    if (sum(degseq)%%2 != 0) {
        degseq[1] <- degseq[1] + 1
    }

    if (dist == "power-law") {
        # if the specified distribution type is power-law, return the degree sequence
        return(degseq)

    } else if (dist == "log-normal") {
        # if the specified distribution type is log-normal, generate a degree sequence following
        # this distribution; but with a same mean degree <k> as its 'scale-free' counterpart. the
        # spread alpha = 1.

        # calculate mean degree in sequence
        mean_deg <- mean(degseq)

        # generate raw log-normal degree sequence
        raw_degseq <- rlnorm(n = n, meanlog = log(mean_deg), sdlog = 1)

        # re-scale the degree sequence to match the target mean degree
        scaling_fctr <- mean_deg/mean(raw_degseq)
        scaled_degseq <- raw_degseq * scaling_fctr

        # apply bounds [k_min, k_max] and round to integer values
        bounded_degseq <- pmin(pmax(round(scaled_degseq), k_min), k_max)

        # since the mean may shift after rounding and bounding, re-scale again:
        scaling_fctr2 <- mean_deg/mean(bounded_degseq)
        degseq <- pmin(pmax(round(bounded_degseq * scaling_fctr2), k_min), k_max)

        # correct the degree sequence if its sum is odd (necessary for the configuration model)
        if (sum(degseq)%%2 != 0) {
            degseq[1] <- degseq[1] + 1
        }
        return(degseq)
    } else {
        stop("Invalid distribution type. Please choose 'power-law' or 'log-normal'.")
    }
}
n = 96  # number of agents
p_t = 0.1  # proportion 'trendsetters'

# plotting window
par(mfrow = c(1, 2))

for (i in c("power-law", "log-normal")) {
    # generate degree sequence using a seed
    degseq <- fdegseq(n = n, dist = i, alpha = 2.7, k_min = 3, seed = 123)

    # retrieve <k>
    mean_deg <- mean(degseq)

    # to graph object, based on the Viger-Lapaty algorithm
    network <- sample_degseq(degseq, method = "vl")

    # randomly assign roles to nodes, based on proportion trendsetters
    V(network)$role <- sample(c(rep("trendsetter", floor(n * p_t)), rep("conformist", n - floor(n * p_t))))

    # plot the graph
    fplot_graph(network, main = paste(i, "degree distribution\n", "<k>=", round(mean_deg, 2)))
}

3.2 erdos-renyi random graph

Produce random networks with the same average degree (<k>) as scale-free counterparts (by tweaking edge probability p):

  • <k> = p * (n - 1)
  • p = <k> / (n - 1)
# loop over a higher number of simulated configuration model to get an accurate estimate of average
# degree <k> in scale-free networks
nIter <- 1000
alphas <- c(2.4, 2.7, 3)
kmean <- list()

for (alpha in alphas) {
    kmean[[as.character(alpha)]] <- numeric(nIter)
    for (i in 1:nIter) {
        try({
            degseq <- fdegseq(n = 96, alpha = alpha, k_min = 3)
            network <- sample_degseq(degseq, method = "vl")
            kmean[[as.character(alpha)]][i] <- mean(degree(network))
        }, silent = TRUE)
    }
}

plist <- list(mean(kmean$`2.4`[kmean$`2.4` != 0])/(n - 1), mean(kmean$`2.7`[kmean$`2.7` != 0])/(n - 1),
    mean(kmean$`3`[kmean$`3` != 0])/(n - 1))

par(mfrow = c(1, length(plist)), mar = c(1, 1, 3, 1))

for (i in 1:length(plist)) {
    network <- erdos.renyi.game(96, p = plist[[i]])
    V(network)$role <- sample(c(rep("trendsetter", floor(n * p_t)), rep("conformist", n - floor(n * p_t))))

    fplot_graph(network, main = paste("Random Network\n", "p =", round(plist[[i]], 3)))

    # hist(degree(network), main = paste0('Degree distribution', ' (<k> = ',
    # #round(mean(degree(network)),2), ')') )
}


3.3 stealing algorithm

But ensure k_min is met by randomly “stealing” ties for nodes with k<k_min, from source nodes (k>k_min) to target nodes:

frandkmin <- function(n, p, k_min = 3, max_iter = 1000, verbose = TRUE, seed = NULL) {

    if (!is.null(seed)) {
        set.seed(seed)
    }

    # ER network
    network <- erdos.renyi.game(n, p)

    iter <- 0
    while (iter < max_iter) {
        deg <- degree(network)

        # look for nodes whose degree is less than k_min
        nodes_below_kmin <- which(deg < k_min)

        if (length(nodes_below_kmin) == 0) {
            if (verbose)
                print(paste("Iteration", iter + 1, ": All nodes have degree >= k_min. Stopping..."))

            break
        }

        # process nodes that dont meet k_min
        for (i in nodes_below_kmin) {
            if (deg[i] >= k_min)
                next
            if (verbose)
                print(paste("Node", i, "has degree", deg[i], "< k_min. Stealing degree..."))

            # find a node j with degree > k_min
            neighbors_i <- neighbors(network, i)  # get neighbors of node i
            candidates_j <- setdiff(which(deg > k_min), nodes_below_kmin)  # nodes with degree > k_min and not already below k_min

            if (length(candidates_j) == 0) {
                if (verbose)
                  print(paste("No candidate j found for node", i, ". Skipping."))
                next
            }

            j <- sample(candidates_j, 1)  # sample one node j from candidates

            if (verbose)
                print(paste("Node", j, "is a candidate for stealing degree."))

            # find a neighbor m of node j to 'steal' the edge from
            neighbors_j <- neighbors(network, j)
            m <- sample(neighbors_j, 1)  # Randomly select one of j's neighbors

            if (deg[m] <= k_min) {
                if (verbose)
                  print(paste("Node", m, "has degree <= k_min. Skipping this neighbor."))
                next
            }

            if (verbose)
                print(paste("Node", j, "is connected to node", m, ". Stealing edge."))

            # check if the edge exists between j and m before attempting to remove it
            edge_exists <- any(ends(network, E(network))[, 1] == j & ends(network, E(network))[, 2] ==
                m | ends(network, E(network))[, 1] == m & ends(network, E(network))[, 2] == j)

            if (edge_exists) {
                # remove the edge between j and m, and add an edge between i and m
                network <- delete_edges(network, get.edge.ids(network, c(j, m)))  # use edge ID instead
                network <- add_edges(network, c(i, m))  # ddd the edge between i and m

                # update the degree vector
                deg <- degree(network)

                if (verbose)
                  print(paste("Node", i, "now has degree", deg[i]))
            } else {
                if (verbose)
                  print(paste("No edge exists between node", j, "and node", m, "Skipping..."))
            }
        }

        # increment iteration counter
        iter <- iter + 1
        if (verbose)
            print(paste("Iteration", iter, "completed."))
    }

    return(network)
}

network <- frandkmin(n = 96, p = 0.06, k_min = 3, verbose = TRUE, seed = 143)
#> [1] "Node 20 has degree 2 < k_min. Stealing degree..."
#> [1] "Node 9 is a candidate for stealing degree."
#> [1] "Node 9 is connected to node 68 . Stealing edge."
#> [1] "Node 20 now has degree 3"
#> [1] "Node 24 has degree 2 < k_min. Stealing degree..."
#> [1] "Node 41 is a candidate for stealing degree."
#> [1] "Node 41 is connected to node 27 . Stealing edge."
#> [1] "Node 24 now has degree 3"
#> [1] "Node 36 has degree 2 < k_min. Stealing degree..."
#> [1] "Node 42 is a candidate for stealing degree."
#> [1] "Node 42 is connected to node 43 . Stealing edge."
#> [1] "Node 36 now has degree 3"
#> [1] "Node 58 has degree 2 < k_min. Stealing degree..."
#> [1] "Node 18 is a candidate for stealing degree."
#> [1] "Node 18 is connected to node 21 . Stealing edge."
#> [1] "Node 58 now has degree 3"
#> [1] "Node 78 has degree 2 < k_min. Stealing degree..."
#> [1] "Node 96 is a candidate for stealing degree."
#> [1] "Node 96 is connected to node 75 . Stealing edge."
#> [1] "Node 78 now has degree 3"
#> [1] "Iteration 1 completed."
#> [1] "Iteration 2 : All nodes have degree >= k_min. Stopping..."
table(degree(network))
#> 
#>  3  4  5  6  7  8  9 10 12 13 
#> 13 15 12 12 17 11 10  3  1  2

3.4 small-world

network <- sample_smallworld(dim = 1, size = 96, nei = 3, p = 0.7)
V(network)$role <- sample(c(rep("trendsetter", floor(n * p_t)), rep("conformist", n - floor(n * p_t))))
fplot_graph(network, main = "small-world network")

# plot degree distributions side-by-side
er <- erdos.renyi.game(n, p = 0.01)
sw <- sample_smallworld(dim = 1, size = n, nei = 3, p = 0.1)
sf <- sample_degseq(fdegseq(n = n, alpha = 2.7, k_min = 3), method = "vl")
ln <- sample_degseq(fdegseq(n = n, alpha = 2.7, dist = "log-normal", k_min = 3), method = "vl")

par(mfrow = c(1, 4))
plot(degree_distribution(er), pch = 20, xlab = "k", ylab = "P(k)", las = 1, main = "Random graph", log = "xy")
plot(degree_distribution(sw), pch = 20, xlab = "k", ylab = "P(k)", las = 1, main = "Small-world graph",
    log = "xy")
plot(degree_distribution(sf), pch = 20, xlab = "k", ylab = "P(k)", las = 1, main = "Scale-free graph",
    log = "xy")
plot(degree_distribution(ln), pch = 20, xlab = "k", ylab = "P(k)", las = 1, main = "Log-normal distribution",
    log = "xy")


3.5 rewiring algorithm

Rewiring algorithm to tune degree (dis)assortativity (Newman 2002):

frewire_r <- function(network, target_r, max_iter = 1e+05, tol = 0.01, verbose = TRUE) {

    current_r <- assortativity_degree(network)
    iteration <- 1

    if (verbose) {
        cat("Target assortativity coefficient:", target_r, "\n")
        cat("Starting assortativity coefficient:", current_r, "\n")
        cat("Tolerance:", tol, "\n")
    }

    while (abs(current_r - target_r) > tol && iteration < max_iter) {

        # get network edges
        edges <- E(network)
        # to edgelist
        edge_list <- ends(network, edges)

        # randomly select two pairs of connected nodes
        idx1 <- sample(1:nrow(edge_list), 1)
        idx2 <- sample(1:nrow(edge_list), 1)

        # extract node indices
        u1 <- edge_list[idx1, 1]  # node 1 of first edge
        v1 <- edge_list[idx1, 2]  # node 2 of first edge
        u2 <- edge_list[idx2, 1]  # etc
        v2 <- edge_list[idx2, 2]

        # check if the two pairs of connected nodes (u1, v1; u2, v2) are disjoint
        if (length(unique(c(u1, v1, u2, v2))) == 4) {
            # check if there is already an edge across the node-pairs ensure no loops and no
            # duplicate edges
            if (!are_adjacent(network, u1, u2) && !are_adjacent(network, v1, v2) && u1 != v2 && u2 !=
                v1) {

                # perform the edge swap (u1,v1) <-> (u2,v2) becomes (u1,v2) <-> (u2,v1)
                new_network <- network  # copy network

                # check if the new edges already exist to avoid duplicates
                if (!are_adjacent(new_network, u1, v2) && !are_adjacent(new_network, u2, v1)) {
                  # add edges
                  new_network <- add_edges(new_network, c(u1, v2, u2, v1))
                  # remove edges
                  new_network <- delete_edges(new_network, get.edge.ids(new_network, c(u1, v1, u2, v2)))

                  # new assortativity
                  new_r <- assortativity_degree(new_network)

                  # accept tie swap if it brings us closer to the target assortativity
                  if (abs(new_r - target_r) < abs(current_r - target_r)) {
                    current_r <- new_r
                    network <- new_network
                    if (verbose) {
                      cat("Rewiring at iteration", iteration, "brought assortativity closer to target! Current assortativity coefficient:",
                        new_r, "\n")
                    }
                  }
                }
            }
        }
        iteration <- iteration + 1
    }

    if (verbose) {
        cat("Final assortativity coefficient:", current_r, "\n")
        if (abs(current_r - target_r) <= tol) {
            cat("Target reached within tolerance.\n")
        } else {
            cat("Reached maximum iterations without meeting target.\n")
        }
    }

    return(network)
}


Apply the function:

# initialize scale-free network
network <- sample_degseq(fdegseq(n = n, alpha = 2.7, k_min = 3, seed = 123), method = "vl")

# get current assortativity coefficient
assortativity_degree(network)
#> [1] -0.1485165
# set new targets
frewire_r(network, target_r = -0.2, max_iter = 1000)
#> Target assortativity coefficient: -0.2 
#> Starting assortativity coefficient: -0.1485165 
#> Tolerance: 0.01 
#> Rewiring at iteration 21 brought assortativity closer to target! Current assortativity coefficient: -0.1487372 
#> Rewiring at iteration 23 brought assortativity closer to target! Current assortativity coefficient: -0.1497303 
#> Rewiring at iteration 26 brought assortativity closer to target! Current assortativity coefficient: -0.1509993 
#> Rewiring at iteration 33 brought assortativity closer to target! Current assortativity coefficient: -0.1592753 
#> Rewiring at iteration 34 brought assortativity closer to target! Current assortativity coefficient: -0.1594408 
#> Rewiring at iteration 41 brought assortativity closer to target! Current assortativity coefficient: -0.1601029 
#> Rewiring at iteration 61 brought assortativity closer to target! Current assortativity coefficient: -0.1607098 
#> Rewiring at iteration 84 brought assortativity closer to target! Current assortativity coefficient: -0.1609305 
#> Rewiring at iteration 90 brought assortativity closer to target! Current assortativity coefficient: -0.1617581 
#> Rewiring at iteration 116 brought assortativity closer to target! Current assortativity coefficient: -0.161896 
#> Rewiring at iteration 130 brought assortativity closer to target! Current assortativity coefficient: -0.1629995 
#> Rewiring at iteration 138 brought assortativity closer to target! Current assortativity coefficient: -0.164103 
#> Rewiring at iteration 147 brought assortativity closer to target! Current assortativity coefficient: -0.1642133 
#> Rewiring at iteration 152 brought assortativity closer to target! Current assortativity coefficient: -0.1661444 
#> Rewiring at iteration 154 brought assortativity closer to target! Current assortativity coefficient: -0.1661996 
#> Rewiring at iteration 156 brought assortativity closer to target! Current assortativity coefficient: -0.1663375 
#> Rewiring at iteration 157 brought assortativity closer to target! Current assortativity coefficient: -0.1667237 
#> Rewiring at iteration 172 brought assortativity closer to target! Current assortativity coefficient: -0.1672203 
#> Rewiring at iteration 175 brought assortativity closer to target! Current assortativity coefficient: -0.1678823 
#> Rewiring at iteration 178 brought assortativity closer to target! Current assortativity coefficient: -0.1692617 
#> Rewiring at iteration 179 brought assortativity closer to target! Current assortativity coefficient: -0.1695927 
#> Rewiring at iteration 182 brought assortativity closer to target! Current assortativity coefficient: -0.1792756 
#> Rewiring at iteration 185 brought assortativity closer to target! Current assortativity coefficient: -0.1797998 
#> Rewiring at iteration 192 brought assortativity closer to target! Current assortativity coefficient: -0.1798274 
#> Rewiring at iteration 195 brought assortativity closer to target! Current assortativity coefficient: -0.182586 
#> Rewiring at iteration 208 brought assortativity closer to target! Current assortativity coefficient: -0.1837171 
#> Rewiring at iteration 213 brought assortativity closer to target! Current assortativity coefficient: -0.1839654 
#> Rewiring at iteration 214 brought assortativity closer to target! Current assortativity coefficient: -0.1840757 
#> Rewiring at iteration 233 brought assortativity closer to target! Current assortativity coefficient: -0.1848482 
#> Rewiring at iteration 236 brought assortativity closer to target! Current assortativity coefficient: -0.1849033 
#> Rewiring at iteration 241 brought assortativity closer to target! Current assortativity coefficient: -0.1855654 
#> Rewiring at iteration 242 brought assortativity closer to target! Current assortativity coefficient: -0.1858964 
#> Rewiring at iteration 244 brought assortativity closer to target! Current assortativity coefficient: -0.1881034 
#> Rewiring at iteration 247 brought assortativity closer to target! Current assortativity coefficient: -0.1900069 
#> Final assortativity coefficient: -0.1900069 
#> Target reached within tolerance.
#> IGRAPH bf4f112 U--- 96 267 -- Degree sequence random graph
#> + attr: name (g/c), method (g/c)
#> + edges from bf4f112:
#>   [1]  1--26  1--24  1--10  2--24  2--87  2--31  2--11  2--53  2-- 3  3--66  3--24  4--84  4--78
#>  [14]  4--47  4--32  4--67  4--34  4--92  4--24  5--24  5--23  5--94  5--38  5--55  5--54  5--91
#>  [27]  5--34  5-- 9  5--80  5--60  5--11  6--89  6--81  6--59  7--53  7--61  8--36  8--24  8--53
#>  [40]  8--87  8--62  8--16  8--72  9--59  9--50 10--11 10--94 11--95 11--38 11--73 11--35 11--16
#>  [53] 11--22 11--47 11--72 11--23 11--62 11--24 11--63 11--81 12--31 12--24 13--43 13--55 13--51
#>  [66] 14--95 15--24 16--75 16--93 16--48 16--66 16--63 16--24 17--69 18--32 18--91 19--40 19--87
#>  [79] 19--28 20--32 20--45 20--82 20--77 20--31 20--60 20--61 20--52 20--44 21--28 21--31 21--68
#>  [92] 21--42 21--24 21--53 21--33 22--24 22--67 23--27 23--90 24--45 24--33 24--87 24--54 24--44
#> + ... omitted several edges
# also for random network with similar density
dens <- mean(degree(network))/(n - 1)

network <- frandkmin(n = n, p = dens, k_min = 3, seed = 123)
#> [1] "Node 18 has degree 2 < k_min. Stealing degree..."
#> [1] "Node 50 is a candidate for stealing degree."
#> [1] "Node 50 is connected to node 8 . Stealing edge."
#> [1] "Node 18 now has degree 3"
#> [1] "Node 23 has degree 2 < k_min. Stealing degree..."
#> [1] "Node 96 is a candidate for stealing degree."
#> [1] "Node 96 is connected to node 7 . Stealing edge."
#> [1] "Node 23 now has degree 3"
#> [1] "Node 31 has degree 2 < k_min. Stealing degree..."
#> [1] "Node 86 is a candidate for stealing degree."
#> [1] "Node 86 is connected to node 80 . Stealing edge."
#> [1] "Node 31 now has degree 3"
#> [1] "Node 32 has degree 1 < k_min. Stealing degree..."
#> [1] "Node 86 is a candidate for stealing degree."
#> [1] "Node 86 is connected to node 44 . Stealing edge."
#> [1] "Node 32 now has degree 2"
#> [1] "Node 35 has degree 2 < k_min. Stealing degree..."
#> [1] "Node 91 is a candidate for stealing degree."
#> [1] "Node 91 is connected to node 70 . Stealing edge."
#> [1] "Node 35 now has degree 3"
#> [1] "Node 49 has degree 1 < k_min. Stealing degree..."
#> [1] "Node 78 is a candidate for stealing degree."
#> [1] "Node 78 is connected to node 76 . Stealing edge."
#> [1] "Node 49 now has degree 2"
#> [1] "Node 57 has degree 2 < k_min. Stealing degree..."
#> [1] "Node 69 is a candidate for stealing degree."
#> [1] "Node 69 is connected to node 60 . Stealing edge."
#> [1] "Node 57 now has degree 3"
#> [1] "Node 73 has degree 1 < k_min. Stealing degree..."
#> [1] "Node 10 is a candidate for stealing degree."
#> [1] "Node 10 is connected to node 76 . Stealing edge."
#> [1] "Node 73 now has degree 2"
#> [1] "Iteration 1 completed."
#> [1] "Node 32 has degree 2 < k_min. Stealing degree..."
#> [1] "Node 28 is a candidate for stealing degree."
#> [1] "Node 28 is connected to node 55 . Stealing edge."
#> [1] "Node 32 now has degree 3"
#> [1] "Node 49 has degree 2 < k_min. Stealing degree..."
#> [1] "Node 85 is a candidate for stealing degree."
#> [1] "Node 85 is connected to node 4 . Stealing edge."
#> [1] "Node 49 now has degree 3"
#> [1] "Node 73 has degree 2 < k_min. Stealing degree..."
#> [1] "Node 24 is a candidate for stealing degree."
#> [1] "Node 24 is connected to node 40 . Stealing edge."
#> [1] "Node 73 now has degree 3"
#> [1] "Iteration 2 completed."
#> [1] "Iteration 3 : All nodes have degree >= k_min. Stopping..."
assortativity_degree(network)
#> [1] 0.1024573
frewire_r(network, target_r = -0.2, max_iter = 1000)
#> Target assortativity coefficient: -0.2 
#> Starting assortativity coefficient: 0.1024573 
#> Tolerance: 0.01 
#> Rewiring at iteration 5 brought assortativity closer to target! Current assortativity coefficient: 0.09459182 
#> Rewiring at iteration 6 brought assortativity closer to target! Current assortativity coefficient: 0.08410446 
#> Rewiring at iteration 13 brought assortativity closer to target! Current assortativity coefficient: 0.07099527 
#> Rewiring at iteration 18 brought assortativity closer to target! Current assortativity coefficient: 0.0640037 
#> Rewiring at iteration 22 brought assortativity closer to target! Current assortativity coefficient: 0.06138186 
#> Rewiring at iteration 24 brought assortativity closer to target! Current assortativity coefficient: 0.04827267 
#> Rewiring at iteration 27 brought assortativity closer to target! Current assortativity coefficient: 0.03516348 
#> Rewiring at iteration 28 brought assortativity closer to target! Current assortativity coefficient: 0.02642402 
#> Rewiring at iteration 29 brought assortativity closer to target! Current assortativity coefficient: 0.01943245 
#> Rewiring at iteration 30 brought assortativity closer to target! Current assortativity coefficient: 0.01681061 
#> Rewiring at iteration 34 brought assortativity closer to target! Current assortativity coefficient: 0.01156693 
#> Rewiring at iteration 35 brought assortativity closer to target! Current assortativity coefficient: 0.009819042 
#> Rewiring at iteration 36 brought assortativity closer to target! Current assortativity coefficient: 0.003701419 
#> Rewiring at iteration 38 brought assortativity closer to target! Current assortativity coefficient: 0.001079581 
#> Rewiring at iteration 39 brought assortativity closer to target! Current assortativity coefficient: -0.005911988 
#> Rewiring at iteration 46 brought assortativity closer to target! Current assortativity coefficient: -0.0137775 
#> Rewiring at iteration 47 brought assortativity closer to target! Current assortativity coefficient: -0.03562616 
#> Rewiring at iteration 48 brought assortativity closer to target! Current assortativity coefficient: -0.038248 
#> Rewiring at iteration 52 brought assortativity closer to target! Current assortativity coefficient: -0.04873535 
#> Rewiring at iteration 54 brought assortativity closer to target! Current assortativity coefficient: -0.05048324 
#> Rewiring at iteration 57 brought assortativity closer to target! Current assortativity coefficient: -0.06097059 
#> Rewiring at iteration 58 brought assortativity closer to target! Current assortativity coefficient: -0.07932346 
#> Rewiring at iteration 59 brought assortativity closer to target! Current assortativity coefficient: -0.08981082 
#> Rewiring at iteration 63 brought assortativity closer to target! Current assortativity coefficient: -0.1160292 
#> Rewiring at iteration 65 brought assortativity closer to target! Current assortativity coefficient: -0.1212729 
#> Rewiring at iteration 67 brought assortativity closer to target! Current assortativity coefficient: -0.1221468 
#> Rewiring at iteration 69 brought assortativity closer to target! Current assortativity coefficient: -0.1291384 
#> Rewiring at iteration 70 brought assortativity closer to target! Current assortativity coefficient: -0.1308863 
#> Rewiring at iteration 71 brought assortativity closer to target! Current assortativity coefficient: -0.1448694 
#> Rewiring at iteration 77 brought assortativity closer to target! Current assortativity coefficient: -0.1474913 
#> Rewiring at iteration 79 brought assortativity closer to target! Current assortativity coefficient: -0.1562307 
#> Rewiring at iteration 80 brought assortativity closer to target! Current assortativity coefficient: -0.1649702 
#> Rewiring at iteration 87 brought assortativity closer to target! Current assortativity coefficient: -0.1667181 
#> Rewiring at iteration 90 brought assortativity closer to target! Current assortativity coefficient: -0.1719618 
#> Rewiring at iteration 94 brought assortativity closer to target! Current assortativity coefficient: -0.1737096 
#> Rewiring at iteration 97 brought assortativity closer to target! Current assortativity coefficient: -0.1763315 
#> Rewiring at iteration 100 brought assortativity closer to target! Current assortativity coefficient: -0.1833231 
#> Rewiring at iteration 102 brought assortativity closer to target! Current assortativity coefficient: -0.1876928 
#> Rewiring at iteration 104 brought assortativity closer to target! Current assortativity coefficient: -0.1903146 
#> Final assortativity coefficient: -0.1903146 
#> Target reached within tolerance.
#> IGRAPH bf72b88 U--- 96 272 -- Erdos-Renyi (gnp) graph
#> + attr: name (g/c), type (g/c), loops (g/l), p (g/n)
#> + edges from bf72b88:
#>   [1]  2-- 7  3-- 7  5--10 12--15  8--16  2--17 17--21 12--22 20--22 16--24  9--26  8--27 18--27
#>  [14]  8--28 15--30 11--31 16--31 21--32  6--33 16--33 20--33  2--36  5--36 10--36 22--37 24--37
#>  [27]  1--38  7--38 27--38  4--39 17--39 35--39 33--40 14--41 19--41  1--42 14--42 22--42 30--42
#>  [40]  1--43  7--43 23--44  6--45 26--45 29--45  6--46 17--46 22--46 41--46 45--46 14--47 17--47
#>  [53] 20--47  1--48 13--48 28--48 22--50 37--50  6--51 36--51  8--52 11--52 37--53  5--54 51--55
#>  [66] 44--57  4--58 26--58 27--58 40--58 52--58 15--59 18--60 37--61 48--61 11--62 19--62 30--62
#>  [79] 14--63  2--64 60--64 20--66 33--66 54--66  3--67  9--67 10--67 34--67  2--68  8--68 29--68
#>  [92] 50--68 20--69 50--69 66--69 67--69 29--70 40--70  5--71  6--71 61--71 63--71 10--72 13--72
#> + ... omitted several edges
V(network)$role <- sample(c(rep("trendsetter", floor(n * p_t)), rep("conformist", n - floor(n * p_t))))

3.6 swapping algorithm

Swapping algorithm to tune degree-trait correlation (Lerman, Yan, and Wu 2016):

# we manipulate degree-trait correlation (rho) by swapping attribute values among the nodes. To
# increase \rho_{kx}, we randomly choose nodes v_1 with x=1 and v_0 with x=0 and swap their
# attributes if the degree of the degree of v_0 is greater than that of v_1 (until the desired
# \rho_{kx} is reached; or it no longer changes).
fdegtraitcor <- function(network) {
    roles <- ifelse(V(network)$role == "trendsetter", 1, 0)
    degrees <- degree(network)
    return(list(cor = cor(roles, degrees), roles = roles, degrees = degrees))
}

# swapping function to adjust degree-trait correlation
fswap_rho <- function(network, target_rho, max_iter = 1000, tol = 0.05, verbose = TRUE) {

    current <- fdegtraitcor(network)
    iteration <- 1
    best_network <- network
    best_rho <- current$cor

    if (verbose) {
        cat("Target degree-trait correlation:", target_rho, "\n")
        cat("Starting degree-trait correlation:", current$cor, "\n")
        cat("Tolerance:", tol, "\n\n")
    }

    while (iteration <= max_iter) {
        # check if we are already within tolerance
        if (abs(current$cor - target_rho) <= tol) {
            if (verbose)
                cat("Target reached within tolerance at iteration", iteration, ".\n")
            break
        }

        # randomly select nodes for swapping
        v1 <- sample(which(current$roles == 1), 1)
        v0 <- sample(which(current$roles == 0), 1)

        # get degrees of selected nodes
        k1 <- current$degrees[v1]
        k0 <- current$degrees[v0]

        # swap roles if condition k_v0 > k_v1 is met
        if (k0 > k1) {
            current$roles[v1] <- 0
            current$roles[v0] <- 1

            # update graph roles
            V(network)$role <- ifelse(current$roles == 1, "trendsetter", "conformist")

            # recalculate degree-trait correlation
            current <- fdegtraitcor(network)

            # check if this is the closest correlation to the target so far
            if (abs(current$cor - target_rho) < abs(best_rho - target_rho)) {
                best_network <- network
                best_rho <- current$cor
                if (verbose) {
                  cat("Trait-swapping at iteration", iteration, "brought correlation closer to target! Current correlation:",
                    current$cor, "\n")
                }
            }
        }
        iteration <- iteration + 1
    }

    # check if the final correlation is worse than the best correlation
    final_correlation <- current$cor
    if (abs(final_correlation - target_rho) > abs(best_rho - target_rho)) {
        if (verbose) {
            cat("\nWarning: Final iteration made the correlation worse. Reverting to best observed correlation.\n")
        }
    }

    if (verbose) {
        cat("\nFinal degree-trait correlation:", best_rho, "\n")
        if (abs(best_rho - target_rho) <= tol) {
            cat("Target reached within tolerance.\n")
        } else if (iteration > max_iter) {
            cat("Reached maximum iterations without meeting target.\n")
        }
    }
    return(best_network)
}


Apply the function:

# get current degree-trait correlation
fdegtraitcor(network)
#> $cor
#> [1] 0.07172364
#> 
#> $roles
#>  [1] 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0
#> [49] 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0
#> 
#> $degrees
#>  [1]  5  8  4  6  6  6 10 10  3 10  7  6  4  8  5  9  6  3  4  7  6  8  3  3  5  4  5  3  8  4  3  3
#> [33]  9  3  3  7  9  4  5  5  6  6  5  4  5 11  6  6  3  4  8  6  4  4  4  5  3  8  7  7  7  5  6  4
#> [65]  3  6  7  7  5  7  8  5  3  4  3  5  6  8  5  4  5  5  4  5  3  4  5  7  9  9  8  7  5  9  6  6
# set new target
target = 0.5

fdegtraitcor(fswap_rho(network = network, target_rho = target, tol = 0.01))
#> Target degree-trait correlation: 0.5 
#> Starting degree-trait correlation: 0.07172364 
#> Tolerance: 0.01 
#> 
#> Trait-swapping at iteration 6 brought correlation closer to target! Current correlation: 0.08965455 
#> Trait-swapping at iteration 10 brought correlation closer to target! Current correlation: 0.1434473 
#> Trait-swapping at iteration 11 brought correlation closer to target! Current correlation: 0.1793091 
#> Trait-swapping at iteration 12 brought correlation closer to target! Current correlation: 0.2151709 
#> Trait-swapping at iteration 19 brought correlation closer to target! Current correlation: 0.2510328 
#> Trait-swapping at iteration 24 brought correlation closer to target! Current correlation: 0.2689637 
#> Trait-swapping at iteration 25 brought correlation closer to target! Current correlation: 0.3048255 
#> Trait-swapping at iteration 28 brought correlation closer to target! Current correlation: 0.3406873 
#> Trait-swapping at iteration 31 brought correlation closer to target! Current correlation: 0.3586182 
#> Trait-swapping at iteration 40 brought correlation closer to target! Current correlation: 0.4303419 
#> Trait-swapping at iteration 59 brought correlation closer to target! Current correlation: 0.4662037 
#> Trait-swapping at iteration 77 brought correlation closer to target! Current correlation: 0.4841346 
#> Trait-swapping at iteration 94 brought correlation closer to target! Current correlation: 0.5020655 
#> Target reached within tolerance at iteration 95 .
#> 
#> Final degree-trait correlation: 0.5020655 
#> Target reached within tolerance.
#> $cor
#> [1] 0.5020655
#> 
#> $roles
#>  [1] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0
#> [49] 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0
#> 
#> $degrees
#>  [1]  5  8  4  6  6  6 10 10  3 10  7  6  4  8  5  9  6  3  4  7  6  8  3  3  5  4  5  3  8  4  3  3
#> [33]  9  3  3  7  9  4  5  5  6  6  5  4  5 11  6  6  3  4  8  6  4  4  4  5  3  8  7  7  7  5  6  4
#> [65]  3  6  7  7  5  7  8  5  3  4  3  5  6  8  5  4  5  5  4  5  3  4  5  7  9  9  8  7  5  9  6  6


Simulate networks (here, scale-free with alpha = 2.7) with independently varying degree assortativity and degree-trait correlation:

par(mfrow = c(3, 3))

# NETWORKS
alpha = 2.7
degseq <- fdegseq(n = 96, alpha = alpha, k_min = 3, seed = 2352)
network <- sample_degseq(degseq, method = "vl")
V(network)$role <- sample(c(rep("trendsetter", floor(n * p_t)), rep("conformist", n - floor(n * p_t))))

network1 <- frewire_r(network, target_r = -0.1, verbose = FALSE)

fplot_graph(network1, main = paste0("configuration model\ndegree sequence generated from p(k)~k^{-α}\nN=96; prop. trendsetter = 0.1; α=2.7; k_min=3\nr_kk = ",
    round(assortativity_degree(network1), 3), "; p_kx = ", round(fdegtraitcor(network1)$cor, 3)))

network2 <- frewire_r(network, target_r = -0.3, verbose = FALSE)

fplot_graph(network2, main = paste0("configuration model\ndegree sequence generated from p(k)~k^{-α}\nN=96; prop. trendsetter = 0.1; α=2.7; k_min=3\nr_kk = ",
    round(assortativity_degree(network2), 3), "; p_kx = ", round(fdegtraitcor(network2)$cor, 3)))

network3 <- fswap_rho(network2, target_rho = 0.5, verbose = FALSE)

fplot_graph(network3, main = paste0("configuration model\ndegree sequence generated from p(k)~k^{-α}\nN=96; prop. trendsetter = 0.1; α=2.7; k_min=3\nr_kk = ",
    round(assortativity_degree(network3), 3), "; p_kx = ", round(fdegtraitcor(network3)$cor, 3)))

# DEGREE ASSORTATIVITY

plot(degree(network), knn(network)$knn, pch = 19, xlab = "Node degree (k)", ylab = "Average neighbor degree (<k'>)",
    main = "degree assortativity")
plot(degree(network2), knn(network2)$knn, pch = 19, xlab = "Node degree (k)", ylab = "Average neighbor degree (<k'>)",
    main = "degree assortativity")
plot(degree(network3), knn(network3)$knn, pch = 19, xlab = "Node degree (k)", ylab = "Average neighbor degree (<k'>)",
    main = "degree assortativity")

# VIOLIN PLOT OF DEGREE DISTRIBUTION PER ROLE

# extract node degrees
degrees <- degree(network1)
trendsetter_degrees <- degrees[V(network1)$role == "trendsetter"]
conformist_degrees <- degrees[V(network1)$role == "conformist"]

vioplot(trendsetter_degrees, conformist_degrees, names = c("Trendsetters", "Conformists"), col = c("#800080",
    "#FFD700"), main = "degree distribution")

degrees2 <- degree(network2)
trendsetter_degrees2 <- degrees2[V(network2)$role == "trendsetter"]
conformist_degrees2 <- degrees2[V(network2)$role == "conformist"]

vioplot(trendsetter_degrees2, conformist_degrees2, names = c("Trendsetters", "Conformists"), col = c("#800080",
    "#FFD700"), main = "degree distribution")

degrees3 <- degree(network3)
trendsetter_degrees3 <- degrees3[V(network3)$role == "trendsetter"]
conformist_degrees3 <- degrees3[V(network3)$role == "conformist"]
vioplot(trendsetter_degrees3, conformist_degrees3, names = c("Trendsetters", "Conformists"), col = c("#800080",
    "#FFD700"), main = "degree distribution")


Specify a parameter space to generate networks with independently varied degree distribution, degree assortativity, degree-trait correlation:

# structural parameters
n = 96
k_min = 3
p_t = 0.1

# target parameters
alphas <- c(2.4, 2.7, 3)
distributions <- c("power-law", "log-normal")
target_r_values <- list(seq(-0.4, -0.15, by = 0.05), seq(-0.35, -0.05, by = 0.05), seq(-0.3, 0.1, by = 0.05))
names(target_r_values) <- alphas
target_rho_values <- seq(0, 0.6, by = 0.1)

# list for results
results <- list()

for (dist in distributions) {
    for (alpha in alphas) {

        # generate degree sequence from specified distribution and alpha
        degseq <- fdegseq(n = n, dist = dist, alpha = alpha, k_min = k_min, seed = 123)

        # create an undirected, connected, simple graph using Viger-Latapy algorithm
        network <- sample_degseq(degseq, method = "vl")

        # assign roles randomly, based on proportion trendsetter p_t
        V(network)$role <- sample(c(rep("trendsetter", floor(n * p_t)), rep("conformist", n - floor(n *
            p_t))))

        for (target_r in target_r_values[[as.character(alpha)]]) {
            # loop over target_r values
            rewired_network <- frewire_r(network, target_r, verbose = FALSE)
            actual_r <- assortativity_degree(rewired_network)

            for (target_rho in target_rho_values) {
                # loop over target_rho values
                final_network <- fswap_rho(rewired_network, target_rho, verbose = FALSE)
                final_rho <- fdegtraitcor(final_network)$cor

                # and assume that agents act according to their role agents don't observe each
                # others' roles, but do observe actions (based on which they can infer
                # roles/preferences...)

                V(final_network)$action <- ifelse(V(final_network)$role == "trendsetter", 1, 0)

                # store results
                results <- append(results, list(list(distribution = dist, alpha = alpha, target_r = target_r,
                  actual_r = actual_r, target_rho = target_rho, actual_rho = final_rho, network = final_network)))
            }
        }
    }
}


Sample some simulations from the ‘network universe’:

# excluding the graph object:
lapply(sample(results, 5), function(x) {
    z <- x  # copy
    z$network <- NULL
    z
})
#> [[1]]
#> [[1]]$distribution
#> [1] "log-normal"
#> 
#> [[1]]$alpha
#> [1] 2.7
#> 
#> [[1]]$target_r
#> [1] -0.25
#> 
#> [[1]]$actual_r
#> [1] -0.2516837
#> 
#> [[1]]$target_rho
#> [1] 0.5
#> 
#> [[1]]$actual_rho
#> [1] 0.4841843
#> 
#> 
#> [[2]]
#> [[2]]$distribution
#> [1] "log-normal"
#> 
#> [[2]]$alpha
#> [1] 2.7
#> 
#> [[2]]$target_r
#> [1] -0.3
#> 
#> [[2]]$actual_r
#> [1] -0.2920339
#> 
#> [[2]]$target_rho
#> [1] 0.4
#> 
#> [[2]]$actual_rho
#> [1] 0.3513395
#> 
#> 
#> [[3]]
#> [[3]]$distribution
#> [1] "power-law"
#> 
#> [[3]]$alpha
#> [1] 3
#> 
#> [[3]]$target_r
#> [1] -0.25
#> 
#> [[3]]$actual_r
#> [1] -0.240705
#> 
#> [[3]]$target_rho
#> [1] 0.2
#> 
#> [[3]]$actual_rho
#> [1] 0.14159
#> 
#> 
#> [[4]]
#> [[4]]$distribution
#> [1] "power-law"
#> 
#> [[4]]$alpha
#> [1] 3
#> 
#> [[4]]$target_r
#> [1] -0.1
#> 
#> [[4]]$actual_r
#> [1] -0.09125596
#> 
#> [[4]]$target_rho
#> [1] 0.5
#> 
#> [[4]]$actual_rho
#> [1] 0.4929644
#> 
#> 
#> [[5]]
#> [[5]]$distribution
#> [1] "log-normal"
#> 
#> [[5]]$alpha
#> [1] 2.4
#> 
#> [[5]]$target_r
#> [1] -0.2
#> 
#> [[5]]$actual_r
#> [1] -0.1954962
#> 
#> [[5]]$target_rho
#> [1] 0.2
#> 
#> [[5]]$actual_rho
#> [1] 0.1673623

4 “majority illusion”

A function to calculate the magnitude of the “majority illusion”, based on the network structure, and a given majority threshold (here, “strong influence”, so half of neighbors need to adopt the trend in order for an ego to be persuaded to do the same):

calculate_majority_illusion <- function(network, threshold = 0.49) {
    roles <- V(network)$role
    actions <- V(network)$action

    # initialize counter for majority illusion
    mi_count <- 0

    # loop over conformists
    for (v in V(network)) {
        if (roles[v] == "conformist") {
            neighbors <- neighbors(network, v)
            trend_neighbors <- sum(actions[neighbors] == 1)
            prop_trend <- trend_neighbors/length(neighbors)

            if (prop_trend > threshold) {
                # under 'weak influence', more than half of the neighborhood suffices; so set
                # threshold at .5
                mi_count <- mi_count + 1
            }
        }
    }
    # return fraction of conformists who have majority illusion
    return(mi_count/sum(roles == "conformist"))
}


Apply the function over our ‘network universe’, where each network corresponds to a specific parameter space row, generated using a unique seed:

plotdata <- do.call(rbind, lapply(results, function(res) {
    dist <- factor(res$dist, levels = c("power-law", "log-normal"))
    alpha <- res$alpha
    r <- res$actual_r
    rho <- res$actual_rho
    network <- res$network

    # calculate the majority illusion (i.e., the proportion of conformists whose neighbors meet or
    # exceed threshold φ)
    mi <- calculate_majority_illusion(network)

    # create a data-frame
    data.frame(dist = dist, alpha = alpha, r = r, rho = rho, mi = mi)
}))

# make separate data-frames for each level of alpha
alpha1 <- plotdata[plotdata$alpha == unique(plotdata$alpha)[1], ]
alpha2 <- plotdata[plotdata$alpha == unique(plotdata$alpha)[2], ]
alpha3 <- plotdata[plotdata$alpha == unique(plotdata$alpha)[3], ]

# create bins for r (deg-assorativity)
fcreate_bins <- function(data, variable = "r", out = 6) {
    rvals <- data[[variable]]
    quant <- quantile(rvals, probs = seq(0, 1, length.out = out))
    # generate labels dynamically
    labels <- sapply(1:(length(quant) - 1), function(i) {
        paste0(round(quant[i], 2), " < r ≤ ", round(quant[i + 1], 2))
    })
    # add categories to the dataset
    data$r_cats <- cut(rvals, breaks = quant, include.lowest = TRUE, labels = labels)
    return(data)
}

# apply binning to each subset
alpha1 <- fcreate_bins(alpha1)
alpha2 <- fcreate_bins(alpha2)
alpha3 <- fcreate_bins(alpha3)

plot1 <- ggplot(alpha1, aes(x = rho, y = mi, color = as.factor(r_cats))) + facet_wrap(~dist, nrow = 2) +
    geom_point(alpha = 0.7, size = 2) + geom_smooth(se = FALSE, method = "loess") + scale_y_continuous(limits = c(-0.05,
    0.7)) + labs(x = expression("degree-trait correlation (" ~ rho[kx] ~ ")"), y = "prop. conformists w/ prop. trendsetter nbh. > φ",
    color = expression("degree assortativity (" ~ r[kk] ~ ")")) + theme(legend.position = c(0.3, 0.35)) +
    ggtitle(paste0("α=", alpha1$alpha))

plot2 <- ggplot(alpha2, aes(x = rho, y = mi, color = as.factor(r_cats))) + facet_wrap(~dist, nrow = 2) +
    geom_point(alpha = 0.7, size = 2) + geom_smooth(se = FALSE, method = "loess") + scale_y_continuous(limits = c(-0.05,
    0.7)) + labs(x = expression("degree-trait correlation (" ~ rho[kx] ~ ")"), y = "prop. conformists w/ prop. trendsetter nbh. > φ",
    color = expression("degree assortativity (" ~ r[kk] ~ ")")) + theme(legend.position = c(0.3, 0.35)) +
    ggtitle(paste0("α=", alpha2$alpha))

plot3 <- ggplot(alpha3, aes(x = rho, y = mi, color = as.factor(r_cats))) + facet_wrap(~dist, nrow = 2) +
    geom_point(alpha = 0.7, size = 2) + geom_smooth(se = FALSE, method = "loess") + scale_y_continuous(limits = c(-0.05,
    0.7)) + labs(x = expression("degree-trait correlation (" ~ rho[kx] ~ ")"), y = "prop. conformists w/ prop. trendsetter nbh. > φ",
    color = expression("degree assortativity (" ~ r[kk] ~ ")")) + theme(legend.position = c(0.3, 0.35)) +
    ggtitle(paste0("α=", alpha3$alpha))

# combine
ggarrange(plot1, plot2, plot3, ncol = 3) + ggtitle(" \"Majority illusion\" in networks with a power-law and log-normal degree distribution") +
    theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 16))


Now for each parameter space row generate networks over N different seeds:

n = 96 # number of agents
p_t_values <- c(0.05, 0.1, 0.2) # proportion "trendsetters"
k_min = 3
# target parameters
alphas <- c(2.4, 2.7, 3)
distributions <- c("power-law", "log-normal", "binomial")
target_r_values <- list(seq(-0.4, -0.15, by = 0.05),
                        seq(-0.35, -0.05, by = 0.05),
                        seq(-0.3, 0.1, by = 0.05)
                        )
names(target_r_values) <- alphas
target_rho_values <- seq(0, 0.6, by = 0.1)

# set up parallel backend to increase efficiency
ncores <- detectCores() - 1 
cl <- makeCluster(ncores)
registerDoParallel(cl)

# number of seeds
nIter <- 50

# list to store results in
rep_results <- list()

# parallel processing using foreach
rep_results <- foreach(alpha = alphas, .combine = 'c', .packages = c("igraph")) %:%
  foreach(p_t = p_t_values, .combine = 'c', .packages = c("igraph")) %:%
  foreach(iter = 1:nIter, .combine = 'c', .packages = c("igraph")) %dopar% {
    
    # seed must vary with each iteration
    seed <- 123 + iter
    set.seed(seed)
  
    print(paste0("Running iteration ", iter, "/", nIter, " for alpha = ", alpha, ", p_t = ", p_t))
    
    results <- list()  #temporary storage for the results
    
    # loop over distribution types:
    for (dist in distributions) {
      
      if (dist == "binomial") { # if random network; first get a target <k> from the corresponding power-law network
        degseq <- fdegseq(n = n, alpha = alpha, k_min = k_min, seed = 123 + iter, dist = "power-law")
        dens <- mean(degseq)/(n-1)
        
        # generate random network, meeting k_min, with 'fixed' density
        network <- frandkmin(n=n, p=dens, k_min=3, verbose=FALSE, seed = 123 + iter)
      } else {
        
        # generate degree sequence with specified distribution type
        degseq <- fdegseq(n = n, alpha = alpha, k_min = k_min, seed = 123 + iter, dist = dist)
      
        # construct the network
        network <- sample_degseq(degseq, method = "vl")
        
      }
      # assign roles randomly
      V(network)$role <- sample(c(rep("trendsetter", floor(n * p_t)), rep("conformist", n - floor(n * p_t))))
      
      # loop over target_r values
      for (target_r in target_r_values[[as.character(alpha)]]) {
        rewired_network <- frewire_r(network, target_r, verbose = FALSE, max_iter = 1e4)
        actual_r <- assortativity_degree(rewired_network)
        
        # loop over target_rho values
        for (target_rho in target_rho_values) {
          final_network <- fswap_rho(rewired_network, target_rho, verbose = FALSE)
          final_rho <- fdegtraitcor(final_network)$cor
          
          # update choices (choice == role): 
          V(final_network)$action <- ifelse(V(final_network)$role == "trendsetter", 1, 0)
          
          # calculate global majority illusion
          mi <- calculate_majority_illusion(final_network)
          
          # append results
          results <- append(results, list(list(
            p_t = p_t,
            dist = dist,
            alpha = alpha,
            target_r = target_r,
            actual_r = actual_r,
            target_rho = target_rho,
            actual_rho = final_rho,
            mi = mi,
            seed = seed, # store seed for reproducibility
            network = final_network # store networks, so we can access these for the simulations.
          )))
        }
      }
    }
    results
  }

stopCluster(cl)

#save output..
fsave(rep_results, "networks.Rda")


Visualize the extent of majority illusion depending on the network parameters across two degree distribution types, based on network simulations with different seeds. Visualizations include both observed MI and predicted values from OLS regression.

nets <- fload("./data/processed/20250207networks.Rda")

# to a dataframe:
df <- do.call(rbind, lapply(nets, function(res) {
  data.frame(
    seed = res$seed,
    p_t = res$p_t,
    dist = res$dist,
    alpha = res$alpha,
    target_r = res$target_r,
    actual_r = res$actual_r,
    target_rho = res$target_rho,
    actual_rho = res$actual_rho,
    mi = res$mi
  )
}))

# create a 3D scatterplot of observed values of MI, disaggregated by
# degree distribution type

# only networks with p_t=10
df2 <- df[df$p_t==.1, ]

#@RF: but here we can tweak p_t (showing that in random networks MI also occurs when p_t is sufficiently large.)


fig1 <- plot_ly()

for (dist in unique(df2$dist)) {
  fig1 <- fig1 %>%
    add_trace(
      data = df2[df2$dist == dist, ],
      x = ~alpha,
      y = ~actual_rho,
      z = ~actual_r,
      color = ~mi,
      type = 'scatter3d',
      mode = 'markers',
      marker = list(size = 3),
      name = as.character(dist),
      hovertemplate = paste(
        "α: %{x}<br>",
        "ρ_{xk}: %{y}<br>",
        "r_{kk}: %{z}<br>",
        "MI: %{marker.color}<extra></extra>"
      )
    ) 
}

# add layout details:
fig1 <- fig1 %>%
  layout(
    title = '3D scatterplot of the magnitude of the "majority illusion" by degree distribution type',
    scene = list(
      xaxis = list(title = "α"),
      yaxis = list(title = "ρ_{xk}"),
      zaxis = list(title = "r_{kk}")
    )
  ) %>%
  colorbar(title="Majority illusion")


#fit models => predicted values
m1 <- lm(mi ~ actual_rho + actual_r + as.factor(alpha), data = df2[df2$dist == "power-law",])
m2 <- lm(mi ~ actual_rho + actual_r + as.factor(alpha) + actual_rho:actual_r, data = df2[df2$dist == "power-law",])
m3 <- lm(mi ~ actual_rho + actual_r + as.factor(alpha) + actual_rho:actual_r + actual_rho:as.factor(alpha) + actual_r:as.factor(alpha), data = df2[df2$dist == "power-law",])

#anova(m1,m2,m3)
#summary(m3)

# create a sequence of values for rho, r, and alpha
rho_vals <- seq(min(df2[df2$dist == "power-law",]$actual_rho), max(df2[df2$dist == "power-law",]$actual_rho), length.out = 50)
r_vals <- seq(min(df2[df2$dist == "power-law",]$actual_r), max(df2[df2$dist == "power-law",]$actual_r), length.out = 50)
#alpha_vals <- seq(2.4, 3, by=0.1)  
alpha_vals <- c(2.4,2.7,3)

# function to generate the surface data for a given alpha value
fsurfacedat <- function(alpha_val) {
  # create a grid of r and rho values
  grid <- expand.grid(actual_r = r_vals, actual_rho = rho_vals)
  
  # fixed alpha value
  grid$alpha <- alpha_val
  
  # predict the values of mi using the model
  grid$mi_pred <- predict(m3, newdata = grid)
  
  # reshape the predicted values into a matrix
  mi_matrix <- matrix(grid$mi_pred, nrow = length(r_vals), ncol = length(rho_vals), byrow = TRUE)
  
  return(mi_matrix)
}

# precompute the surface data for all alpha values
surface_data_list <- lapply(alpha_vals, fsurfacedat)

# find the global range of MM (z values)
z_min <- min(sapply(surface_data_list, min))
z_max <- max(sapply(surface_data_list, max))

# create the initial surface plot with the first alpha value
fig2 <- plot_ly(
  x = r_vals,  # x-axis: r
  y = rho_vals,  # y-axis: rho
  z = surface_data_list[[1]],  # surface data for the first alpha
  type = "surface",
  colorbar = list(
    title = "MI",
    cmin = z_min, 
    cmax = z_max
  ),
  cmin = z_min,  
  cmax = z_max,
  hovertemplate = paste(
    "r_{kk}: %{x:.2f}<br>",  
    "ρ_{xk}: %{y:.2f}<br>",  
    "MI: %{z:.2f}<extra></extra>"
  )
)

# add slider for dynamic alpha selection
fig2 <- fig2 %>%
  layout(
    title = paste('Predicted "majority illusion" in scale-free network from OLS model for α =', alpha_vals[1]),
    scene = list(
      xaxis = list(title = "r_{kk}"),
      yaxis = list(title = "ρ_{kx}"),
      zaxis = list(
        title = "MI",
        range = c(z_min, z_max) 
      )
    ),
    sliders = list(
      list(
        active = 0,  # start with the first alpha value
        steps = lapply(seq_along(alpha_vals), function(i) {
          list(
            method = "update",
            args = list(
              list(
                z = list(surface_data_list[[i]])  #update surface data
              ),
              list(
                title = paste('Predicted "majority illusion" in scale-free network from OLS model for α =', alpha_vals[i])  # update title
              )
            ),
            label = as.character(alpha_vals[i])  # slider label
          )
        }),
        currentvalue = list(
          prefix = "α: ",  # text displayed next to the slider
          font = list(size = 16)
        )
      )
    )
  )  %>%
  colorbar(title="Majority illusion")

fig1
fig2

5 simulation function

Now that we have our “starting networks”, let’s simulate the evolution of an unpopular norm using our utility function:

fabm <- function(network = network, # the generated network
                 rounds = 10, # number of timesteps/rounds
                 choice_rule = "deterministic", # choice update rule
                 utility_fn = futility, # the utility function
                 params = list(s=15, e=10, w=40, z=50, lambda1=4.3, lamda2=1.8), # utility parameters
                 mi_threshold = ifelse(params$z > 50, .49, .50), # under the "strong influence" condition (i.e., 50<z<90) half of neighbors adopting the trend is sufficient; under the "weak influence" condition (i.e., 30<z<50) *more* than half of neighbors adopting the trend is needed to be influenced.)
                 histories = FALSE, # return decision history
                 outcome = TRUE, # return outcomes
                 plot = FALSE ) { # return plot
  
  # make an agents dataframe
  agents <- tibble(
    id = 1:length(network),
    role = V(network)$role,
    preference = ifelse(role == "trendsetter", 1, 0), # 1 = follow trend, 0 = not follow
    choice = NA
  )
  
  # initialize decision history
  decision_history <- tibble()
  
  # also initialize an equilibrium flag
  equilibrium_reached <- FALSE
  equilibrium_t <- NA
  
  # simulation loop
  for (t in 1:rounds) {
    if (t == 1) {
      # round 1: agents make decisions based on private preferences (no social information available yet)
      agents <- agents %>%
        mutate(choice = preference)
    } else {
       # round t > 1: agents make decisions based on social information from neighbors (decisions at t-1)
      agents <- agents %>%
        rowwise() %>%
        mutate(
          util_1 = utility_fn(id, 1, agents, network, list(s = params$s, e = params$e, w = params$w, z = params$z, lambda1 = params$lambda1, lambda2 = params$lambda2))$utility,
          util_0 = utility_fn(id, 0, agents, network, list(s = params$s, e = params$e, w = params$w, z = params$z, lambda1 = params$lambda1, lambda2 = params$lambda2))$utility,
          # make decision based on expected utility
          choice = ifelse(
            choice_rule == "deterministic",
            ifelse(util_1 > util_0, 1, 0),  # deterministic rule
            sample(c(1, 0), size = 1, prob = c(exp(util_1), exp(util_0)))  # probabilistic rule (@RF: only for conformists? level of noise?)
          )
        )
    }
    # store the decisions for this round
    decision_history <- bind_rows(decision_history, agents %>% 
                                    mutate(round = t))
  }
  
  # check for equilibrium
  # @RF: when introducing some stochasticity, this should be handled differently...
  for(t in 2:rounds ) { 
    prev_trend_followers <- mean(decision_history %>% filter(round == t - 1) %>% pull(choice) == 1)
    curr_trend_followers <- mean(decision_history %>% filter(round == t) %>% pull(choice) == 1)
        
    if (!equilibrium_reached && abs(curr_trend_followers - prev_trend_followers) < .Machine$double.eps) {
        equilibrium_reached <- TRUE
        equilibrium_t <- t - 1  
      }
    }

  # based on decision_history:
  
  # 1. calculate global majority illusion over rounds
  globMI <- numeric(rounds)
  for (t in 1:rounds) {
    if (t == 1) {
      # in round 1: no social information, so no MI
      globMI[t] <- NA
    } else {
      # rounds t > 1: calculate magnitude of majority illusion
      # first, make a copy of the network object
      exposure_network <- network
      # update the actions of actors, based on their choices in the previous round
      # after all, actors don't observe others' roles, but only their choices,
      # based on which they can infer their role
      V(exposure_network)$action <- decision_history[decision_history$round == t - 1,]$choice
      globMI[t] <- calculate_majority_illusion(exposure_network, threshold = mi_threshold)  
    }
  }
  
  # to long format
  MI <- tibble(
    round = 1:rounds,
    outcome = "majority_illusion",
    statistic = globMI
    )
  
  # 2. calculate the evolution of the unpopular norm
  UN <- decision_history %>%
    group_by(round) %>%
    summarise(
      follow_trend = mean(choice == 1, na.rm = TRUE)
      ) %>%
      pivot_longer(cols = c("follow_trend"),
                   names_to = "outcome",
                   values_to = "statistic")

  # bind
  plotdata <- bind_rows(MI, UN)
  
  if (plot) {
    fig <- ggplot(plotdata, aes(x=round, y=statistic, color=factor(outcome))) +
      geom_line() +
      geom_point() +
      scale_y_continuous(labels = scales::percent_format(scale = 100), limits = c(0,1)) +
      scale_x_continuous(breaks = seq(1, max(plotdata$round), by = 1)) +
     labs(
       title = "Evolution of an unpopular norm",
       subtitle = "`follow_trend` denotes the percentage of all agents that follow the trend.\n`majority_illusion` reflects the percentage of conformists whose neighbors meet or exceed the adoption threshold φ (i.e., 0.50),\nwith 'strong influence' referring to both meeting and exceeding the threshold, and 'weak influence' to exceeding it only.\nThe grey dashed line reflects the percentage of agents whose role is trendsetter. The purple circle depicts the equilibrium point.",
       x = "round",
       y = "% agents",
       color = "outcome") +
     theme(panel.grid.minor.x = element_blank(),
            #legend.position = "bottom",
            plot.subtitle = element_text(size = 8)) +
    geom_hline(yintercept = prop.table(table(agents$role))[2], linetype = "dashed", color = "darkgrey", size = 1)
    
    # add a circle around the equilibrium state 'follow_trend' statistic
   if (!is.na(equilibrium_t)) {
    fig <- fig + geom_point(
      data = plotdata %>% filter(round == equilibrium_t & outcome == "follow_trend"),
      aes(x = round, y = statistic), 
      shape = 1, size = 4, color = "purple", stroke = 2
    )
  }
}
  
  # return outputs
  output <- list()
  if (histories) { 
    output$decision_history <- decision_history}
  
  if (outcome) { 
    output$outcomes <- plotdata
    output$equilibrium <- list(
      reached = equilibrium_reached,
      round = equilibrium_t,
      prop_follow_trend = if (!is.na(equilibrium_t)) {
        mean(decision_history %>% filter(round == equilibrium_t) %>% pull(choice) == 1)
      } else { NA }
    )
    }
  if (plot) { output$plot <- fig}
  return(output)
}

#test <- fabm(network=nets[[49]]$network, rounds=10, params=list(s=10, e=10, w=30, z=40), histories = TRUE, plot = TRUE)
#test$plot

test <- fabm(network=nets[[sample(length(nets),1)]]$network, rounds=10, params=list(s=15, e=10, w=40, z=50, lambda1=4.3, lambda2=1.8), histories = TRUE, plot = TRUE)
test$plot


5.1 example

Select random network from the networks with the top 10% highest majority illusion and simulate the evolution of the trend:

set.seed(235435)  # set seed for reproducibility 

mis <- sapply(nets, function(x) x$mi)  # extract MI values
sorted <- order(mis)  # sort indices by MI
n <- length(mis)

top10 <- sorted[ceiling(0.9 * n):n]  # top 10% MI
ind <- sample(top10, 10)  # select random elements

# loop through each selection network; inspect the evolution of MI and UP
for (i in ind) {
    # access network and statistics
    network <- nets[[i]]$network
    dist <- nets[[i]]$dist
    alpha <- nets[[i]]$alpha
    actual_r <- nets[[i]]$actual_r
    actual_rho <- nets[[i]]$actual_rho
    mi <- nets[[i]]$mi

    # print statistics
    cat("Selected network", i, "\n")
    cat("Distribution:", dist, "\n")
    if (dist == "power-law") {
        cat("α:", alpha, "\n")
    }
    cat("<k>:", mean(degree(network)), "\n")
    cat("r_{kk}:", actual_r, "\n")
    cat("ρ_{kx}:", actual_rho, "\n")
    cat("Starting MI:", mi, "\n")

    cat("Running simulation ...\n")
    print(fabm(network = network, rounds = 20, params = list(s = 15, e = 10, w = 40, z = 50, lambda1 = 4.3,
        lambda2 = 1.8), plot = TRUE)$plot)
}

6 simulations

# clean our working environement, but keep our functions, our dataframe, and our list of starting
# networks
rm(list = setdiff(ls(), c("df", "nets", lsf.str())))
gc()

# @RF: let's start not with all networks... but select for each parameter space row a few seeds
# sub_nets <- Filter(function(x) x$seed %in% c(124:133), nets)

# parameters
rounds = 20

# parallel backend
ncores <- detectCores() - 2
cl <- makeCluster(ncores)
registerDoParallel(cl)

## @RF: in case you stop the simulation; just return from where you left:
start <- 1
start <- max(as.numeric(gsub(".*_(\\d+)\\.rds", "\\1", list.files("./sims")))) + 1

if (!dir.exists("./sims")) dir.create("./sims")

# export custom functions and required objects
clusterExport(cl, c("fabm", "futility", "nets", "rounds", "start"))

system.time({
    foreach(i = start:length(nets), .packages = c("igraph", "tidyverse")) %dopar% {
        # simulation
        sim <- fabm(network = nets[[i]]$network, rounds = rounds, params = list(s = 15, e = 10, w = 40,
            z = 50, lambda1 = 4.3, lambda2 = 1.8))$equilibrium

        # data frame for this network
        sim_results <- data.frame(seed = i, equilibrium = sim$reached, equilibrium_t = sim$round, prop_trend = sim$prop_follow_trend)

        # save intermediate results
        saveRDS(sim_results, file = paste0("./sims/sim_network_", i, ".rds"))  #

        # periodic memory cleanup
        if (i%%200 == 0) {
            gc()
        }

    }
})

stopCluster(cl)
closeAllConnections()
# list all .rds files in the folder
file_paths <- list.files("./sims/", full.names = TRUE)

# extract the numeric part of the file names and order them numerically
file_paths <- file_paths[order(as.numeric(gsub("\\D", "", basename(file_paths))))]

# use lapply to read all .rds files and rbindlist to combine them
system.time(all_data <- data.table::rbindlist(lapply(file_paths, readRDS)))  #

# make sure we only include networks for which we have a simulation extract numeric part of file
# names
file_numbers <- as.numeric(gsub("\\D", "", basename(file_paths)))

sorted_indices <- order(file_numbers)
file_paths <- file_paths[sorted_indices]
file_numbers <- file_numbers[sorted_indices]

subdf <- df[file_numbers, ]

sims <- cbind(subdf, all_data)

# remove those with missing on outcome
mis <- which(is.na(sims$prop_trend))
sims <- sims[-mis, ]

fsave(sims, "sim_dataframe.Rda")
data <- fload("./data/processed/20250212sim_dataframe.Rda")
names(data)[10] <- "i"

hist_obj <- hist(data$prop_trend[data$p_t == 0.1], breaks = seq(0, 1, by = 0.05), plot = FALSE)
colors <- ifelse(hist_obj$mids < 0.15, "skyblue", ifelse(hist_obj$mids < 0.95, "salmon", "palegreen"))

par(mar = c(5, 4, 4, 6))
plot(hist_obj, main = "Histogram of proportion trend followers at equilibrium", sub = "(10% trendsetters)",
    xlab = "Prop. trend followers", xlim = c(0, 1), col = colors, border = "black", freq = TRUE, ylim = c(0,
        max(hist_obj$counts) * 1.1))

# overlay the density plot
par(new = TRUE)
density_obj <- density(data$prop_trend[data$p_t == 0.1])  # kernel density

plot(density_obj, main = "", col = "black", lwd = 2, axes = FALSE, xlab = "", ylab = "", xlim = c(0,
    1))

axis(4, at = pretty(range(density_obj$y)))
mtext("Density", side = 4, line = 2.5)

data$dist <- fct_rev(data$dist)

create_plot <- function(p_t_level) {
    data_filtered <- data %>%
        filter(p_t == p_t_level)

    ggplot(data_filtered, aes(x = actual_rho, y = actual_r, color = prop_trend)) + geom_point(size = 1.5,
        alpha = 0.7) + facet_grid2(rows = vars(dist), cols = vars(alpha), labeller = labeller(alpha = function(x) paste0("α=",
        x))) + scale_color_gradientn(colors = c("lightblue", "yellow", "red", "black"), values = scales::rescale(c(0,
        0.2, 0.5, 0.75, 0.9, 1)), limits = c(0, 1), name = "% agents\nfollowing trend") + labs(x = expression(rho[kx]),
        y = expression(r[kk]), title = paste("proportion trendsetters =", p_t_level)) + theme_minimal() +
        theme(strip.background = element_rect(fill = "grey90", color = "grey50"))
}

# generate plots for each level of p_t
plot1 <- create_plot(unique(data$p_t)[1])
plot2 <- create_plot(unique(data$p_t)[2])
plot3 <- create_plot(unique(data$p_t)[3])

# combine
combined_plot <- (plot1 + plot2 + plot3) + plot_layout(guides = "collect") & theme(legend.position = "bottom")

combined_plot

# a function to create heatmap for a specific level of p_t
create_heatmap <- function(p_t_level) {
  # filter data for the given level of p_t
  data_filtered <- data %>% filter(p_t == p_t_level)
  
  # summarize the data
  data_summary <- data_filtered %>%
    group_by(target_rho, target_r, alpha, dist) %>%
    summarize(
      `P(neg. cascade)` = mean(prop_trend == 1, na.rm = TRUE),  # mean of prop_trend == 1
      n = n(),  # count of observations in each group
      se_prob = sqrt((`P(neg. cascade)` * (1 - `P(neg. cascade)` )) / n),  # standard error for the proportion
      .groups = "drop"
    )
  
  # find the highest value for each facet
  max_values <- data_summary %>%
    group_by(alpha, dist) %>%
    filter(`P(neg. cascade)` == max(`P(neg. cascade)`)) %>%
    distinct(alpha, dist, .keep_all = TRUE) %>%
    ungroup()
  
  # create the heatmap
  ggplot(data_summary, aes(x = target_rho, y = target_r, fill = `P(neg. cascade)`)) +
    geom_tile(color = "white") +  #  with gridlines
    scale_fill_gradientn(
      colors = c("lightblue", "yellow", "red", "black"), 
      name = "P(neg. cascade)",
      limits = c(0, 1)  # ensure the fill scale ranges between 0 and 1
    ) +
    facet_grid2(
      rows = vars(dist),
      cols = vars(alpha),
      scales = "free",
      independent = TRUE,
      labeller = labeller(alpha = function(x) paste0("\u03B1=", x)) 
    ) +
    # add a line connecting the text box to the corresponding tile (drawn first)
    #geom_segment(data = max_values, 
    #             aes(x = target_rho, y = target_r, 
    #                 xend = target_rho - 0.3, yend = target_r),
    #             color = "black", size = 0.5) +  # thin line connecting text and tile
    # add a point at the center of the cell
    #geom_point(data = max_values, 
    #           aes(x = target_rho, y = target_r),
    #           color = "black", size = 1) +  # point (dot) at the center of the cell
    # add a label box with the highest value per panel (light fill color)
    #geom_label(data = max_values, 
    #           aes(label = paste0("P = ", round(`P(neg. cascade)`, 2))),
    #           fill = "gray90", color = "black", size = 4, 
    #           label.padding = unit(0.2, "lines"), label.size = 0.2, 
    #           nudge_x = -0.3, nudge_y = 0) +  # light gray background
    labs(
      x = expression(rho[kx]), 
      y = expression(r[kk]), 
      title = paste("Proportion trendsetters =", p_t_level)  # add a title for each level of p_t
    ) +
    theme_minimal() +
    theme(
      strip.background = element_rect(fill = "grey90", color = "grey50"),
      legend.position = "right"  # move legend to the bottom for consistent layout
    )
}

# generate heatmaps for each level of p_t
heatmap1 <- create_heatmap(unique(data$p_t)[1])
heatmap2 <- create_heatmap(unique(data$p_t)[2])
heatmap3 <- create_heatmap(unique(data$p_t)[3])

# arrange the heatmaps next to each other with a shared legend
combined_heatmap <- (heatmap1 + heatmap2 + heatmap3) +
  plot_layout(guides = "collect") & theme(legend.position = "bottom")

heatmap1

heatmap2

heatmap3

#create_heatmap(unique(data$p_t)[2]) + labs(title=NULL) + theme(legend.position = "bottom")
#ggsave("./figures/neg_cascade_10.png", height=5)

#p5 <- create_heatmap(unique(data$p_t)[1])
#p20 <- create_heatmap(unique(data$p_t)[3])

#(p5 + p20) +
#  plot_layout(guides = "collect") & theme(legend.position = #"bottom")

#ggsave("./figures/neg_cascade_5_20.png", height=5, width=8)
# let's find a candidate netwokr for our experiment: power-law; alpha=2.7, p_t=10, r_kk=-.3,
# rho_kx=0.6

# networks that satsify this:
ind <- as.numeric(rownames(data[data$dist == "power-law" & data$alpha == 2.7 & data$p_t == 0.1 & data$target_r ==
    -0.3 & data$target_rho == 0.6 & data$prop_trend == 1, ]))

for (i in sample(1:length(ind), 5)) {
    graph_plot <- ggplotify::as.grob(~fplot_graph(nets[[ind[i]]]$network))

    abm_result <- fabm(network = nets[[ind[i]]]$network, rounds = 20, params = list(s = 15, e = 10, w = 40,
        z = 50, lambda1 = 4.3, lambda2 = 1.8), plot = TRUE)
    abm_plot <- as.grob(~print(abm_result$plot))

    grid.arrange(graph_plot, abm_plot, ncol = 2)
}

# also a control condition
ind2 <- as.numeric(rownames(data[data$dist == "binomial" & data$alpha == 2.7 & data$p_t == 0.1 & data$target_rho ==
    0.1 & data$target_r == -0.05, ]))

# nets[[ind2[length(ind2)]]][c('target_r', 'target_rho')]

for (i in sample(1:length(ind2), 5)) {
    graph_plot <- ggplotify::as.grob(~fplot_graph(nets[[ind2[i]]]$network))

    abm_result <- fabm(network = nets[[ind2[i]]]$network, rounds = 20, params = list(s = 15, e = 10,
        w = 40, z = 50, lambda1 = 4.3, lambda2 = 1.8), plot = TRUE)
    abm_plot <- as.grob(~print(abm_result$plot))

    grid.arrange(graph_plot, abm_plot, ncol = 2)
}

par(mfrow = c(2, 2), mar = c(2, 2, 1, 1), oma = c(0, 0, 0, 0))

# pick network
network1 <- nets[[ind[1]]]$network

# i want to position the nodes according to their role create a new network object
network_segregated <- network1
E(network_segregated)$weight = 1

# add edges with high weight between all nodes in the same group
for (i in unique(V(network1)$role)) {
    roleV = which(V(network1)$role == i)
    network_segregated = add_edges(network_segregated, combn(roleV, 2), attr = list(weight = 3))
}

# create layout based on network_segregated
set.seed(189)
layout1 = layout_with_fr(network_segregated)

# same for control condition
network2 <- nets[[ind2[1]]]$network
network_segregated <- network2
E(network_segregated)$weight = 1

# add edges with high weight between all nodes in the same group
for (i in unique(V(network2)$role)) {
    roleV = which(V(network2)$role == i)
    network_segregated = add_edges(network_segregated, combn(roleV, 2), attr = list(weight = 1))
}

set.seed(289)
layout2 = layout_with_fr(network_segregated)

fplot_graph(network1, layout_algo = layout1, legend = FALSE, col1 = "purple", col2 = "orange")
mtext("(A)", side = 3, line = -1, at = -0.5, cex = 2)
fplot_graph(network2, layout_algo = layout2, legend = FALSE, col1 = "purple", col2 = "orange")
mtext("(B)", side = 3, line = -1, at = -0.5, cex = 2)
fplot_graph(network1, layout_algo = layout_with_fr(network1), legend = FALSE, col1 = "purple", col2 = "orange")
fplot_graph(network2, layout_algo = layout_with_fr(network2), legend = FALSE, col1 = "purple", col2 = "orange")


References

Lerman, Kristina, Xiaoran Yan, and Xin-Zeng Wu. 2016. “The "Majority Illusion" in Social Networks.” PloS One 11 (2): e0147617. https://doi.org/10.1371/journal.pone.0147617.
Newman, Mark. 2002. “Assortative Mixing in Networks.” Physical Review Letters 89 (20). https://doi.org/10.1103/PhysRevLett.89.208701.
LS0tDQp0aXRsZTogIkFCTSINCmJpYmxpb2dyYXBoeTogcmVmZXJlbmNlcy5iaWINCmxpbmstY2l0YXRpb25zOiB0cnVlDQpkYXRlOiAiTGFzdCBjb21waWxlZCBvbiBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkLSVtLSVZJylgIg0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjc3M6IHR3ZWFrcy5jc3MNCiAgICB0b2M6ICB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgdG9jX2RlcHRoOiA0DQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgY29kZV9kb3dubG9hZDogeWVzDQotLS0NCg0KYGBge3IsIGdsb2JhbHNldHRpbmdzLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCm9wdHNfY2h1bmskc2V0KHRpZHkub3B0cz1saXN0KHdpZHRoLmN1dG9mZj0xMDApLHRpZHk9VFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsY29tbWVudCA9ICIjPiIsIGNhY2hlPVRSVUUsIGNsYXNzLnNvdXJjZT1jKCJ0ZXN0IiksIGNsYXNzLm91dHB1dD1jKCJ0ZXN0MyIpKQ0Kb3B0aW9ucyh3aWR0aCA9IDEwMCkNCnJnbDo6c2V0dXBLbml0cigpDQoNCmNvbG9yaXplIDwtIGZ1bmN0aW9uKHgsIGNvbG9yKSB7c3ByaW50ZigiPHNwYW4gc3R5bGU9J2NvbG9yOiAlczsnPiVzPC9zcGFuPiIsIGNvbG9yLCB4KSB9DQpgYGANCg0KDQpgYGB7ciBrbGlwcHksIGVjaG89RkFMU0UsIGluY2x1ZGU9VFJVRX0NCmtsaXBweTo6a2xpcHB5KHBvc2l0aW9uID0gYygndG9wJywgJ3JpZ2h0JykpDQoja2xpcHB5OjprbGlwcHkoY29sb3IgPSAnZGFya3JlZCcpDQoja2xpcHB5OjprbGlwcHkodG9vbHRpcF9tZXNzYWdlID0gJ0NsaWNrIHRvIGNvcHknLCB0b29sdGlwX3N1Y2Nlc3MgPSAnRG9uZScpDQpgYGANCg0KLS0tICANCg0KIyBHZXR0aW5nIHN0YXJ0ZWQNCg0KVG8gY29weSB0aGUgY29kZSwgY2xpY2sgdGhlIGJ1dHRvbiBpbiB0aGUgdXBwZXIgcmlnaHQgY29ybmVyIG9mIHRoZSBjb2RlLWNodW5rcy4NCg0KIyMgY2xlYW4gdXANCg0KYGBge3IsIGNsZWFuX3VwLCByZXN1bHRzPSdoaWRlJ30NCnJtKGxpc3Q9bHMoKSkNCmdjKCkNCmBgYA0KDQo8YnI+DQoNCiMjIGdlbmVyYWwgY3VzdG9tIGZ1bmN0aW9ucw0KDQotIGBmcGFja2FnZS5jaGVja2A6IENoZWNrIGlmIHBhY2thZ2VzIGFyZSBpbnN0YWxsZWQgKGFuZCBpbnN0YWxsIGlmIG5vdCkgaW4gUg0KLSBgZnNhdmVgOiBGdW5jdGlvbiB0byBzYXZlIGRhdGEgd2l0aCB0aW1lIHN0YW1wIGluIGNvcnJlY3QgZGlyZWN0b3J5DQotIGBmbG9hZGA6IExvYWQgUi1vYmplY3RzIHVuZGVyIG5ldyBuYW1lcw0KLSBgZnNob3dkZmA6IFByaW50IG9iamVjdHMgKHRpYmJsZSAvIGRhdGEuZnJhbWUpIG5pY2VseSBvbiBzY3JlZW4gaW4gLlJtZC4NCi0gYGZwbG90X2dyYXBoYDogdmlzdWFsaXplIHRoZSBuZXR3b3JrIHRvcG9sb2d5IGFuZCBhZ2VudCBwb3NpdGlvbnMgKGRlZ3JlZS10cmFpdCBjb3JyZWxhdGlvbikNCg0KYGBge3IsIGN1c3RvbV9mdW5jdGlvbnMsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KZnBhY2thZ2UuY2hlY2sgPC0gZnVuY3Rpb24ocGFja2FnZXMpIHsNCiAgICBsYXBwbHkocGFja2FnZXMsIEZVTiA9IGZ1bmN0aW9uKHgpIHsNCiAgICAgICAgaWYgKCFyZXF1aXJlKHgsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkpIHsNCiAgICAgICAgICAgIGluc3RhbGwucGFja2FnZXMoeCwgZGVwZW5kZW5jaWVzID0gVFJVRSkNCiAgICAgICAgICAgIGxpYnJhcnkoeCwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQ0KICAgICAgICB9DQogICAgfSkNCn0NCg0KZnNhdmUgPC0gZnVuY3Rpb24oeCwgZmlsZSwgbG9jYXRpb24gPSAiLi9kYXRhL3Byb2Nlc3NlZC8iLCAuLi4pIHsNCiAgICBpZiAoIWRpci5leGlzdHMobG9jYXRpb24pKQ0KICAgICAgICBkaXIuY3JlYXRlKGxvY2F0aW9uKQ0KICAgIGRhdGVuYW1lIDwtIHN1YnN0cihnc3ViKCJbOi1dIiwgIiIsIFN5cy50aW1lKCkpLCAxLCA4KQ0KICAgIHRvdGFsbmFtZSA8LSBwYXN0ZShsb2NhdGlvbiwgZGF0ZW5hbWUsIGZpbGUsIHNlcCA9ICIiKQ0KICAgIHByaW50KHBhc3RlKCJTQVZFRDogIiwgdG90YWxuYW1lLCBzZXAgPSAiIikpDQogICAgc2F2ZSh4LCBmaWxlID0gdG90YWxuYW1lKQ0KfQ0KDQpmbG9hZCA8LSBmdW5jdGlvbihmaWxlTmFtZSkgew0KICAgIGxvYWQoZmlsZU5hbWUpDQogICAgZ2V0KGxzKClbbHMoKSAhPSAiZmlsZU5hbWUiXSkNCn0NCg0KZnNob3dkZiA8LSBmdW5jdGlvbih4LCAuLi4pIHsNCiAgICBrbml0cjo6a2FibGUoeCwgZGlnaXRzID0gMiwgImh0bWwiLCAuLi4pICU+JQ0KICAgICAgICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIpKSAlPiUNCiAgICAgICAga2FibGVFeHRyYTo6c2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIiwgaGVpZ2h0ID0gIjMwMHB4IikNCn0NCg0KZnBsb3RfZ3JhcGggPC0gZnVuY3Rpb24oZ3JhcGgsIG1haW49TlVMTCwgbGF5b3V0X2FsZ289TlVMTCwgDQogICAgICAgICAgICAgICAgICAgICAgICBjb2wxID0gIiM4MDAwODAiLCBjb2wyID0gICIjRkZENzAwIiwgbGVnZW5kPVRSVUUpIHsNCiAgcGxvdChncmFwaCwNCiAgICAgICBtYWluID0gbWFpbiwNCiAgICAgICBsYXlvdXQgPSBsYXlvdXRfYWxnbywNCiAgICAgICB2ZXJ0ZXgubGFiZWwgPSBOQSwNCiAgICAgICB2ZXJ0ZXguc2l6ZSA9IGRlZ3JlZShncmFwaCkqLjUgKyAzLCAjIG5vZGUgc2l6ZSBiYXNlZCBvbiBkZWdyZWUNCiAgICAgICB2ZXJ0ZXguY29sb3IgPSBpZmVsc2UoVihncmFwaCkkcm9sZSA9PSAidHJlbmRzZXR0ZXIiLCBjb2wxLCBjb2wyKSwNCiAgICAgICBlZGdlLndpZHRoID0gMC41LA0KICAgICAgIA0KICAgICAgIGVkZ2UuY29sb3IgPSAiZGFya2dyZXkiKQ0KICAjYWRkIGxlZ2VuZA0KICBpZihsZWdlbmQ9PVRSVUUpIHsNCiAgICBsZWdlbmQoImJvdHRvbWxlZnQiLA0KICAgICAgICAgbGVnZW5kID0gYygiVHJlbmRzZXR0ZXIiLCAiQ29uZm9ybWlzdCIpLA0KICAgICAgICAgcGNoID0gMjEsDQogICAgICAgICBjb2wgPSBjKGNvbDEsY29sMiksDQogICAgICAgICBwdC5iZyA9IGMoY29sMSxjb2wyKSwNCiAgICAgICAgIHB0LmNleCA9IDMsDQogICAgICAgICBidHkgPSAibiIpDQogIH0NCn0NCg0KYGBgDQoNCjxicj4NCg0KIyMgbmVjZXNzYXJ5IHBhY2thZ2VzDQoNCmBgYHtyLCBwYWNrYWdlc30NCnBhY2thZ2VzID0gYygidGlkeXZlcnNlIiwgImlncmFwaCIsICJ2aW9wbG90IiwgImdncGxvdDIiLCAiZ2dwdWJyIiwgInBsb3RseSIsICJwYXJhbGxlbCIsICJmb3JlYWNoIiwgImRvUGFyYWxsZWwiLCAiZ2doNHgiLCAiZ3JpZCIsICJncmlkRXh0cmEiLCAicGF0Y2h3b3JrIiwgImdncGxvdGlmeSIpDQppbnZpc2libGUoZnBhY2thZ2UuY2hlY2socGFja2FnZXMpKQ0Kcm0ocGFja2FnZXMpDQpgYGANCg0KDQotLS0NCg0KIyB1dGlsaXR5IGZ1bmN0aW9uDQoNCkEgZnVuY3Rpb24gdG8gY2FsY3VsYXRlIHRoZSBwYXlvZmYgb2YgYSBzcGVjaWZpYyBjaG9pY2UsIGZvciBhbiBhZ2VudCAoaWQpLCBnaXZlbiB0aGUgYWdlbnRzJyBjdXJyZW50IHN0YXR1c2VzLCB0aGUgbmV0d29yayBzdHJ1Y3R1cmUsIGFuZCB0aGUgdXRpbGl0eSBmdW5jdGlvbiBwYXJhbWV0ZXJzOg0KDQpgYGB7ciwgdXRpbGl0eV9mdW5jdGlvbn0NCmZ1dGlsaXR5IDwtIGZ1bmN0aW9uKGFnZW50X2lkLCBjaG9pY2UsIGFnZW50cywgbmV0d29yaywgcGFyYW1zKSB7DQogICMgZ2V0IGVnbyBhbmQgaGlzIGxvY2FsIG5laWdoYm9yaG9vZA0KICBlZ28gPC0gYWdlbnRzW2FnZW50X2lkLCBdDQogIG5laWdoYm9ycyA8LSBuZWlnaGJvcnMobmV0d29yaywgZWdvJGlkKQ0KICBhbHRlcnMgPC0gYWdlbnRzW2FzLm51bWVyaWMobmVpZ2hib3JzKSwgXQ0KICBuIDwtIG5yb3coYWx0ZXJzKQ0KDQogICMgcHJvcG9ydGlvbiBvZiBuZWlnaGJvcnMgd2hvIGFkb3B0ZWQgKHApIG9yIHJlc2lzdGVkICgxLXApIHRoZSB0cmVuZA0KICBwIDwtIHN1bShhbHRlcnMkY2hvaWNlID09IDEpIC8gbg0KICBxIDwtIDEgLSBwICAjIHByb3BvcnRpb24gb2YgdHJlbmQtcmVzaXN0YW50IG5laWdoYm9ycw0KICANCiAgIyBjYWxjdWxhdGUgZGltaW5pc2hpbmcgcmV0dXJucyBmdW5jdGlvbg0KICBkaW1pbmlzaGluZ19yZXR1cm5zIDwtIGZ1bmN0aW9uKHgsIHYsIGxhbWJkYSkgew0KICAgIHYgKiAoMSAtIGV4cCgtbGFtYmRhICogeCkpIC8gKDEgLSBleHAoLWxhbWJkYSkpDQogIH0NCiAgDQogICMgY2FsY3VsYXRlIGV4cGVjdGVkIHV0aWxpdHkgKGRlcGVuZGluZyBvbiBhbHRlcnMnIGNob2ljZXMgaW4gcHJpb3Igcm91bmQpDQogIGlmKGVnbyRyb2xlID09ICJjb25mb3JtaXN0Iikgew0KICAgIGlmIChjaG9pY2UgPT0gMCkgeyAjIHJlc2lzdCB0aGUgdHJlbmQNCiAgICAgIGNob2ljZV9wYXlvZmYgPC0gcGFyYW1zJHMgIyBmaXhlZCB1dGlsaXR5IGZvciByZXNpc3RpbmcNCiAgICAgIGNvb3JkaW5hdGlvbl9wYXlvZmYgPC0gZGltaW5pc2hpbmdfcmV0dXJucyhxLCBwYXJhbXMkdywgcGFyYW1zJGxhbWJkYTIpDQogICAgfSBlbHNlIHsgI2ZvbGxvdyB0aGUgdHJlbmQNCiAgICAgIGNob2ljZV9wYXlvZmYgPC0gMA0KICAgICAgY29vcmRpbmF0aW9uX3BheW9mZiA8LSBkaW1pbmlzaGluZ19yZXR1cm5zKHAsIHBhcmFtcyR6LCBwYXJhbXMkbGFtYmRhMSkNCiAgICB9DQogIH0gZWxzZSB7ICMgdHJlbmRzZXR0ZXJzIG9ubHkgY2FyZSBhYm91dCBmb2xsb3dpbmcNCiAgICBpZiAoY2hvaWNlID09IDEpIHsNCiAgICAgIGNob2ljZV9wYXlvZmYgPC0gcGFyYW1zJGUNCiAgICB9IGVsc2Ugew0KICAgICAgY2hvaWNlX3BheW9mZiA8LSAwDQogICAgfQ0KICAgIGNvb3JkaW5hdGlvbl9wYXlvZmYgPC0gMA0KICB9DQogIA0KICAjIHJldHVybiB0b3RhbCB1dGlsaXR5IGFuZCBjb3VudHMgb2YgbmVpZ2hib3JzDQogIHJldHVybihsaXN0KHV0aWxpdHkgPSBjaG9pY2VfcGF5b2ZmICsgY29vcmRpbmF0aW9uX3BheW9mZiwNCiAgICAgICAgICAgICAgbjEgPSBzdW0oYWx0ZXJzJGNob2ljZSA9PSAxKSwgICMgY291bnQgb2YgdHJlbmQtYWRvcHRpbmcgbmVpZ2hib3JzDQogICAgICAgICAgICAgIG4wID0gc3VtKGFsdGVycyRjaG9pY2UgPT0gMCkpKSAjIGNvdW50IG9mIHRyZW5kLXJlc2lzdGFudCBuZWlnaGJvcnMNCn0NCmBgYA0KDQoNCi0tLS0NCg0KIyBuZXR3b3JrIHN0cnVjdHVyZQ0KDQojIyBjb25maWd1cmF0aW9uIG1vZGVsDQoNCkEgZnVuY3Rpb24gdG8gZ2VuZXJhdGUgYSBkZWdyZWUgc2VxdWVuY2UgYmFzZWQgb24gYSBwb3dlci1sYXcgLyBsb2ctbm9ybWFsIGRpc3RyaWJ1dGlvbjoNCg0KYGBge3IsIGNvbmZpZ3VyYXRpb25fbW9kZWx9DQpmZGVnc2VxIDwtIGZ1bmN0aW9uKG4sIGRpc3QgPSAicG93ZXItbGF3IiwgYWxwaGEsIGtfbWluID0gMSwga19tYXggPSBuIC0gMSwgc2VlZCA9IE5VTEwpIHsNCiAgDQogIGlmICghaXMubnVsbChzZWVkKSkgeyANCiAgICBzZXQuc2VlZChzZWVkKQ0KICB9DQogIA0KICAjIGdlbmVyYXRlIGEgZGVncmVlIHNlcXVlbmNlIGJhc2VkIG9uIGEgcG93ZXItbGF3IGRpc3RyaWJ1dGlvbiBvZiB0aGUgZm9ybSBwKGspIOKInSBrXnstYWxwaGF9DQogIA0KICAjIGNyZWF0ZSBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24NCiAgcHJvYnMgPC0gKDEgLyAoa19taW46a19tYXgpKV5hbHBoYQ0KICAgIA0KICAjIG5vcm1hbGl6ZSBwcm9iYWJpbGl0aWVzDQogIHByb2JzIDwtIHByb2JzIC8gc3VtKHByb2JzKSANCiAgDQogICMgc2FtcGxlIGEgZGVncmVlIHNlcXVlbmNlDQogIGRlZ3NlcSA8LSBzYW1wbGUoa19taW46a19tYXgsIHNpemUgPSBuLCByZXBsYWNlID0gVFJVRSwgcHJvYiA9ICgxLyhrX21pbjprX21heCkpXmFscGhhKQ0KIA0KICAjIGNvcnJlY3QgdGhlIGRlZ3JlZSBzZXF1ZW5jZSBpZiBpdHMgc3VtIGlzIG9kZCAobmVjZXNzYXJ5IGZvciB0aGUgY29uZmlndXJhdGlvbiBtb2RlbCkNCiAgaWYgKHN1bShkZWdzZXEpICUlIDIgIT0gMCkgew0KICAgIGRlZ3NlcVsxXSA8LSBkZWdzZXFbMV0gKyAxDQogIH0NCiAgDQogIGlmIChkaXN0ID09ICJwb3dlci1sYXciKSB7DQogICAgIyBpZiB0aGUgc3BlY2lmaWVkIGRpc3RyaWJ1dGlvbiB0eXBlIGlzIHBvd2VyLWxhdywgcmV0dXJuIHRoZSBkZWdyZWUgc2VxdWVuY2UNCiAgICByZXR1cm4oZGVnc2VxKQ0KICAgIA0KICB9IGVsc2UgaWYgKGRpc3QgPT0gImxvZy1ub3JtYWwiKSB7DQogICAgIyBpZiB0aGUgc3BlY2lmaWVkIGRpc3RyaWJ1dGlvbiB0eXBlIGlzIGxvZy1ub3JtYWwsIGdlbmVyYXRlIGEgZGVncmVlIHNlcXVlbmNlIGZvbGxvd2luZyB0aGlzIGRpc3RyaWJ1dGlvbjsNCiAgICAjIGJ1dCB3aXRoIGEgc2FtZSBtZWFuIGRlZ3JlZSA8az4gYXMgaXRzICdzY2FsZS1mcmVlJyBjb3VudGVycGFydC4gdGhlIHNwcmVhZCBhbHBoYSA9IDEuIA0KICAgIA0KICAgICMgY2FsY3VsYXRlIG1lYW4gZGVncmVlIGluIHNlcXVlbmNlDQogICAgbWVhbl9kZWcgPC0gbWVhbihkZWdzZXEpDQogICAgDQogICAgIyBnZW5lcmF0ZSByYXcgbG9nLW5vcm1hbCBkZWdyZWUgc2VxdWVuY2UNCiAgICByYXdfZGVnc2VxIDwtIHJsbm9ybShuID0gbiwgbWVhbmxvZyA9IGxvZyhtZWFuX2RlZyksIHNkbG9nID0gMSkNCiAgICANCiAgICAjIHJlLXNjYWxlIHRoZSBkZWdyZWUgc2VxdWVuY2UgdG8gbWF0Y2ggdGhlIHRhcmdldCBtZWFuIGRlZ3JlZQ0KICAgIHNjYWxpbmdfZmN0ciA8LSBtZWFuX2RlZyAvIG1lYW4ocmF3X2RlZ3NlcSkNCiAgICBzY2FsZWRfZGVnc2VxIDwtIHJhd19kZWdzZXEgKiBzY2FsaW5nX2ZjdHINCiAgICANCiAgICAjIGFwcGx5IGJvdW5kcyBba19taW4sIGtfbWF4XSBhbmQgcm91bmQgdG8gaW50ZWdlciB2YWx1ZXMNCiAgICBib3VuZGVkX2RlZ3NlcSA8LSBwbWluKHBtYXgocm91bmQoc2NhbGVkX2RlZ3NlcSksIGtfbWluKSwga19tYXgpDQogICAgDQogICAgIyBzaW5jZSB0aGUgbWVhbiBtYXkgc2hpZnQgYWZ0ZXIgcm91bmRpbmcgYW5kIGJvdW5kaW5nLCByZS1zY2FsZSBhZ2FpbjoNCiAgICBzY2FsaW5nX2ZjdHIyIDwtIG1lYW5fZGVnIC8gbWVhbihib3VuZGVkX2RlZ3NlcSkNCiAgICBkZWdzZXEgPC0gcG1pbihwbWF4KHJvdW5kKGJvdW5kZWRfZGVnc2VxICogc2NhbGluZ19mY3RyMiksIGtfbWluKSwga19tYXgpDQoNCiAgICAjIGNvcnJlY3QgdGhlIGRlZ3JlZSBzZXF1ZW5jZSBpZiBpdHMgc3VtIGlzIG9kZCAobmVjZXNzYXJ5IGZvciB0aGUgY29uZmlndXJhdGlvbiBtb2RlbCkNCiAgICBpZiAoc3VtKGRlZ3NlcSkgJSUgMiAhPSAwKSB7DQogICAgICBkZWdzZXFbMV0gPC0gZGVnc2VxWzFdICsgMQ0KICAgIH0NCiAgICByZXR1cm4oZGVnc2VxKQ0KICB9IA0KICBlbHNlIHsNCiAgICBzdG9wKCJJbnZhbGlkIGRpc3RyaWJ1dGlvbiB0eXBlLiBQbGVhc2UgY2hvb3NlICdwb3dlci1sYXcnIG9yICdsb2ctbm9ybWFsJy4iKQ0KICB9DQp9DQpgYGANCg0KYGBge3J9DQpuID0gOTYgIyBudW1iZXIgb2YgYWdlbnRzDQpwX3QgPSAwLjEgIyBwcm9wb3J0aW9uICJ0cmVuZHNldHRlcnMiDQoNCiMgcGxvdHRpbmcgd2luZG93DQpwYXIobWZyb3c9YygxLDIpKQ0KDQpmb3IgKGkgaW4gYygicG93ZXItbGF3IiwgImxvZy1ub3JtYWwiKSkgew0KICAjIGdlbmVyYXRlIGRlZ3JlZSBzZXF1ZW5jZSB1c2luZyBhIHNlZWQgDQogIGRlZ3NlcSA8LSBmZGVnc2VxKG4gPSBuLCBkaXN0ID0gaSwgYWxwaGEgPSAyLjcsIGtfbWluID0gMywgc2VlZCA9IDEyMykNCiAgDQogICMgcmV0cmlldmUgPGs+DQogIG1lYW5fZGVnIDwtIG1lYW4oZGVnc2VxKQ0KICANCiAgIyB0byBncmFwaCBvYmplY3QsIGJhc2VkIG9uIHRoZSBWaWdlci1MYXBhdHkgYWxnb3JpdGhtDQogIG5ldHdvcmsgPC0gc2FtcGxlX2RlZ3NlcShkZWdzZXEsIG1ldGhvZCA9ICJ2bCIpDQogDQogICMgcmFuZG9tbHkgYXNzaWduIHJvbGVzIHRvIG5vZGVzLCBiYXNlZCBvbiBwcm9wb3J0aW9uIHRyZW5kc2V0dGVycw0KICBWKG5ldHdvcmspJHJvbGUgPC0gc2FtcGxlKGMocmVwKCJ0cmVuZHNldHRlciIsIGZsb29yKG4gKiBwX3QpKSwgcmVwKCJjb25mb3JtaXN0IiwgbiAtIGZsb29yKG4gKiBwX3QpKSkpDQogIA0KICAjIHBsb3QgdGhlIGdyYXBoDQogIGZwbG90X2dyYXBoKG5ldHdvcmssIG1haW4gPSBwYXN0ZShpLCAiZGVncmVlIGRpc3RyaWJ1dGlvblxuIiwgIjxrPj0iLCByb3VuZChtZWFuX2RlZywgMikpKQ0KfQ0KYGBgDQoNCiMjIGVyZG9zLXJlbnlpIHJhbmRvbSBncmFwaA0KDQpQcm9kdWNlIHJhbmRvbSBuZXR3b3JrcyB3aXRoIHRoZSBzYW1lIGF2ZXJhZ2UgZGVncmVlICgmbHQ7ayZndDspIGFzIHNjYWxlLWZyZWUgY291bnRlcnBhcnRzIChieSB0d2Vha2luZyBlZGdlIHByb2JhYmlsaXR5IHApOg0KDQotICZsdDtrJmd0OyA9IHAgKiAobiAtIDEpDQotIHAgPSAmbHQ7ayZndDsgLyAobiAtIDEpDQoNCg0KYGBge3J9DQojIGxvb3Agb3ZlciBhIGhpZ2hlciBudW1iZXIgb2Ygc2ltdWxhdGVkIGNvbmZpZ3VyYXRpb24gbW9kZWwgdG8gZ2V0IGFuIGFjY3VyYXRlIGVzdGltYXRlIG9mIGF2ZXJhZ2UgZGVncmVlIDxrPiBpbiBzY2FsZS1mcmVlIG5ldHdvcmtzDQpuSXRlciA8LSAxZTMNCmFscGhhcyA8LSBjKDIuNCwgMi43LCAzKSAgDQprbWVhbiA8LSBsaXN0KCkgDQoNCmZvciAoYWxwaGEgaW4gYWxwaGFzKSB7DQogIGttZWFuW1thcy5jaGFyYWN0ZXIoYWxwaGEpXV0gPC0gbnVtZXJpYyhuSXRlcikNCiAgZm9yIChpIGluIDE6bkl0ZXIpIHsNCiAgICB0cnkoew0KICAgICAgZGVnc2VxIDwtIGZkZWdzZXEobiA9IDk2LCBhbHBoYSA9IGFscGhhLCBrX21pbiA9IDMpICANCiAgICAgIG5ldHdvcmsgPC0gc2FtcGxlX2RlZ3NlcShkZWdzZXEsIG1ldGhvZCA9ICJ2bCIpICANCiAgICAgIGttZWFuW1thcy5jaGFyYWN0ZXIoYWxwaGEpXV1baV0gPC0gbWVhbihkZWdyZWUobmV0d29yaykpIA0KICAgIH0sIHNpbGVudCA9IFRSVUUpIA0KICB9DQp9DQoNCnBsaXN0IDwtIGxpc3QoDQogIG1lYW4oa21lYW4kYDIuNGBba21lYW4kYDIuNGAhPTBdKSAvIChuLTEpLA0KICBtZWFuKGttZWFuJGAyLjdgW2ttZWFuJGAyLjdgIT0wXSkgLyAobi0xKSwNCiAgbWVhbihrbWVhbiRgM2Bba21lYW4kYDNgIT0wXSkgLyAobi0xKQ0KKQ0KDQpwYXIobWZyb3c9YygxLCBsZW5ndGgocGxpc3QpKSwgbWFyPWMoMSwxLDMsMSkpDQoNCmZvciAoaSBpbiAxOmxlbmd0aChwbGlzdCkpIHsNCiAgbmV0d29yayA8LSBlcmRvcy5yZW55aS5nYW1lKDk2LCBwID0gcGxpc3RbW2ldXSkNCiAgVihuZXR3b3JrKSRyb2xlIDwtIHNhbXBsZShjKHJlcCgidHJlbmRzZXR0ZXIiLCBmbG9vcihuICogcF90KSksIHJlcCgiY29uZm9ybWlzdCIsIG4gLSBmbG9vcihuICogcF90KSkpKQ0KDQogIGZwbG90X2dyYXBoKG5ldHdvcmssIA0KICAgICAgICAgICAgICBtYWluID0gcGFzdGUoIlJhbmRvbSBOZXR3b3JrXG4iLCAicCA9Iiwgcm91bmQocGxpc3RbW2ldXSwgMykpKQ0KICANCiAgI2hpc3QoZGVncmVlKG5ldHdvcmspLCANCiAgICMgICAgbWFpbiA9IHBhc3RlMCgiRGVncmVlIGRpc3RyaWJ1dGlvbiIsICIgKDxrPiA9ICIsICNyb3VuZChtZWFuKGRlZ3JlZShuZXR3b3JrKSksMiksICIpIikNCiMgICAgICAgKQ0KfQ0KYGBgDQoNCjxicj4NCg0KIyMgc3RlYWxpbmcgYWxnb3JpdGhtDQoNCkJ1dCBlbnN1cmUga19taW4gaXMgbWV0IGJ5IHJhbmRvbWx5ICJzdGVhbGluZyIgdGllcyBmb3Igbm9kZXMgd2l0aCBrPGtfbWluLCBmcm9tIHNvdXJjZSBub2RlcyAoaz5rX21pbikgdG8gdGFyZ2V0IG5vZGVzOg0KDQpgYGB7cn0NCmZyYW5ka21pbiA8LSBmdW5jdGlvbihuLCBwLCBrX21pbiA9IDMsIG1heF9pdGVyID0gMWUzLCB2ZXJib3NlID0gVFJVRSwgc2VlZCA9IE5VTEwpIHsNCiAgDQogICBpZiAoIWlzLm51bGwoc2VlZCkpIHsNCiAgICBzZXQuc2VlZChzZWVkKSAgDQogIH0NCiAgDQogICMgRVIgbmV0d29yaw0KICBuZXR3b3JrIDwtIGVyZG9zLnJlbnlpLmdhbWUobiwgcCkNCiAgDQogIGl0ZXIgPC0gMA0KICB3aGlsZSAoaXRlciA8IG1heF9pdGVyKSB7DQogICAgZGVnIDwtIGRlZ3JlZShuZXR3b3JrKQ0KICAgIA0KICAgICMgbG9vayBmb3Igbm9kZXMgd2hvc2UgZGVncmVlIGlzIGxlc3MgdGhhbiBrX21pbg0KICAgIG5vZGVzX2JlbG93X2ttaW4gPC0gd2hpY2goZGVnIDwga19taW4pDQogICAgDQogICAgaWYgKGxlbmd0aChub2Rlc19iZWxvd19rbWluKSA9PSAwKSB7DQogICAgICBpZiAodmVyYm9zZSkgcHJpbnQocGFzdGUoIkl0ZXJhdGlvbiIsIGl0ZXIgKyAxLCAiOiBBbGwgbm9kZXMgaGF2ZSBkZWdyZWUgPj0ga19taW4uIFN0b3BwaW5nLi4uIikpDQogICAgICANCiAgICAgIGJyZWFrDQogICAgfQ0KICAgIA0KICAgICMgcHJvY2VzcyBub2RlcyB0aGF0IGRvbnQgbWVldCBrX21pbg0KICAgIGZvciAoaSBpbiBub2Rlc19iZWxvd19rbWluKSB7DQogICAgICBpZiAoZGVnW2ldID49IGtfbWluKSBuZXh0IA0KICAgICAgaWYgKHZlcmJvc2UpIHByaW50KHBhc3RlKCJOb2RlIiwgaSwgImhhcyBkZWdyZWUiLCBkZWdbaV0sICI8IGtfbWluLiBTdGVhbGluZyBkZWdyZWUuLi4iKSkNCiAgICAgIA0KICAgICAgIyBmaW5kIGEgbm9kZSBqIHdpdGggZGVncmVlID4ga19taW4NCiAgICAgIG5laWdoYm9yc19pIDwtIG5laWdoYm9ycyhuZXR3b3JrLCBpKSAgIyBnZXQgbmVpZ2hib3JzIG9mIG5vZGUgaQ0KICAgICAgY2FuZGlkYXRlc19qIDwtIHNldGRpZmYod2hpY2goZGVnID4ga19taW4pLCBub2Rlc19iZWxvd19rbWluKSAgIyBub2RlcyB3aXRoIGRlZ3JlZSA+IGtfbWluIGFuZCBub3QgYWxyZWFkeSBiZWxvdyBrX21pbg0KICAgICAgDQogICAgICBpZiAobGVuZ3RoKGNhbmRpZGF0ZXNfaikgPT0gMCkgew0KICAgICAgICBpZiAodmVyYm9zZSkgcHJpbnQocGFzdGUoIk5vIGNhbmRpZGF0ZSBqIGZvdW5kIGZvciBub2RlIiwgaSwgIi4gU2tpcHBpbmcuIikpDQogICAgICAgIG5leHQNCiAgICAgIH0NCiAgICAgIA0KICAgICAgaiA8LSBzYW1wbGUoY2FuZGlkYXRlc19qLCAxKSAgIyBzYW1wbGUgb25lIG5vZGUgaiBmcm9tIGNhbmRpZGF0ZXMNCg0KICAgICAgaWYgKHZlcmJvc2UpIHByaW50KHBhc3RlKCJOb2RlIiwgaiwgImlzIGEgY2FuZGlkYXRlIGZvciBzdGVhbGluZyBkZWdyZWUuIikpDQoNCiAgICAgICMgZmluZCBhIG5laWdoYm9yIG0gb2Ygbm9kZSBqIHRvICJzdGVhbCIgdGhlIGVkZ2UgZnJvbQ0KICAgICAgbmVpZ2hib3JzX2ogPC0gbmVpZ2hib3JzKG5ldHdvcmssIGopDQogICAgICBtIDwtIHNhbXBsZShuZWlnaGJvcnNfaiwgMSkgICMgUmFuZG9tbHkgc2VsZWN0IG9uZSBvZiBqJ3MgbmVpZ2hib3JzDQoNCiAgICAgIGlmIChkZWdbbV0gPD0ga19taW4pIHsNCiAgICAgICAgaWYgKHZlcmJvc2UpIHByaW50KHBhc3RlKCJOb2RlIiwgbSwgImhhcyBkZWdyZWUgPD0ga19taW4uIFNraXBwaW5nIHRoaXMgbmVpZ2hib3IuIikpDQogICAgICAgIG5leHQNCiAgICAgIH0NCiAgICAgIA0KICAgICAgaWYgKHZlcmJvc2UpIHByaW50KHBhc3RlKCJOb2RlIiwgaiwgImlzIGNvbm5lY3RlZCB0byBub2RlIiwgbSwgIi4gU3RlYWxpbmcgZWRnZS4iKSkNCiAgICAgIA0KICAgICAgIyBjaGVjayBpZiB0aGUgZWRnZSBleGlzdHMgYmV0d2VlbiBqIGFuZCBtIGJlZm9yZSBhdHRlbXB0aW5nIHRvIHJlbW92ZSBpdA0KICAgICAgZWRnZV9leGlzdHMgPC0gYW55KGVuZHMobmV0d29yaywgRShuZXR3b3JrKSlbLDFdID09IGogJiBlbmRzKG5ldHdvcmssIEUobmV0d29yaykpWywyXSA9PSBtIHwNCiAgICAgICAgICAgICAgICAgICAgICAgICBlbmRzKG5ldHdvcmssIEUobmV0d29yaykpWywxXSA9PSBtICYgZW5kcyhuZXR3b3JrLCBFKG5ldHdvcmspKVssMl0gPT0gaikNCiAgICAgIA0KICAgICAgaWYgKGVkZ2VfZXhpc3RzKSB7DQogICAgICAgICMgcmVtb3ZlIHRoZSBlZGdlIGJldHdlZW4gaiBhbmQgbSwgYW5kIGFkZCBhbiBlZGdlIGJldHdlZW4gaSBhbmQgbQ0KICAgICAgICBuZXR3b3JrIDwtIGRlbGV0ZV9lZGdlcyhuZXR3b3JrLCBnZXQuZWRnZS5pZHMobmV0d29yaywgYyhqLCBtKSkpICAjIHVzZSBlZGdlIElEIGluc3RlYWQNCiAgICAgICAgbmV0d29yayA8LSBhZGRfZWRnZXMobmV0d29yaywgYyhpLCBtKSkgICAgIyBkZGQgdGhlIGVkZ2UgYmV0d2VlbiBpIGFuZCBtDQoNCiAgICAgICAgIyB1cGRhdGUgdGhlIGRlZ3JlZSB2ZWN0b3INCiAgICAgICAgZGVnIDwtIGRlZ3JlZShuZXR3b3JrKQ0KICAgICAgICANCiAgICAgICAgaWYgKHZlcmJvc2UpIHByaW50KHBhc3RlKCJOb2RlIiwgaSwgIm5vdyBoYXMgZGVncmVlIiwgZGVnW2ldKSkNCiAgICAgIH0gZWxzZSB7DQogICAgICAgIGlmICh2ZXJib3NlKSBwcmludChwYXN0ZSgiTm8gZWRnZSBleGlzdHMgYmV0d2VlbiBub2RlIiwgaiwgImFuZCBub2RlIiwgbSwgIlNraXBwaW5nLi4uIikpDQogICAgICB9DQogICAgfQ0KICAgIA0KICAgICMgaW5jcmVtZW50IGl0ZXJhdGlvbiBjb3VudGVyDQogICAgaXRlciA8LSBpdGVyICsgMQ0KICAgIGlmICh2ZXJib3NlKSBwcmludChwYXN0ZSgiSXRlcmF0aW9uIiwgaXRlciwgImNvbXBsZXRlZC4iKSkNCiAgfQ0KICANCiAgcmV0dXJuKG5ldHdvcmspDQp9DQoNCm5ldHdvcmsgPC0gZnJhbmRrbWluKG49OTYsIHA9MC4wNiwga19taW49MywgdmVyYm9zZT1UUlVFLCBzZWVkPTE0MykNCnRhYmxlKGRlZ3JlZShuZXR3b3JrKSkNCmBgYA0KDQojIyBzbWFsbC13b3JsZA0KDQpgYGB7cn0NCm5ldHdvcmsgPC0gc2FtcGxlX3NtYWxsd29ybGQoZGltID0gMSwgc2l6ZSA9IDk2LCBuZWkgPSAzLCBwID0gMC43KQ0KVihuZXR3b3JrKSRyb2xlIDwtIHNhbXBsZShjKHJlcCgidHJlbmRzZXR0ZXIiLCBmbG9vcihuKnBfdCkpLCByZXAoImNvbmZvcm1pc3QiLCBuIC0gZmxvb3IobipwX3QpKSkpDQpmcGxvdF9ncmFwaChuZXR3b3JrLCBtYWluID0gInNtYWxsLXdvcmxkIG5ldHdvcmsiKQ0KYGBgDQoNCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9M30NCiNwbG90IGRlZ3JlZSBkaXN0cmlidXRpb25zIHNpZGUtYnktc2lkZSANCmVyIDwtIGVyZG9zLnJlbnlpLmdhbWUobiwgcD0wLjAxKQ0Kc3cgPC0gc2FtcGxlX3NtYWxsd29ybGQoZGltID0gMSwgc2l6ZSA9IG4sIG5laSA9IDMsIHAgPSAwLjEwKQ0Kc2YgPC0gc2FtcGxlX2RlZ3NlcShmZGVnc2VxKG49biwgYWxwaGE9Mi43LCBrX21pbj0zKSwgbWV0aG9kID0gInZsIikNCmxuIDwtIHNhbXBsZV9kZWdzZXEoZmRlZ3NlcShuPW4sIGFscGhhPTIuNywgZGlzdCA9ICJsb2ctbm9ybWFsIiwga19taW49MyksIG1ldGhvZCA9ICJ2bCIpDQoNCnBhcihtZnJvdz1jKDEsNCkpDQpwbG90KGRlZ3JlZV9kaXN0cmlidXRpb24oZXIpLHBjaD0yMCx4bGFiPSJrIix5bGFiPSJQKGspIixsYXM9MSxtYWluPSJSYW5kb20gZ3JhcGgiLGxvZz0ieHkiKQ0KcGxvdChkZWdyZWVfZGlzdHJpYnV0aW9uKHN3KSxwY2g9MjAseGxhYj0iayIseWxhYj0iUChrKSIsbGFzPTEsbWFpbj0iU21hbGwtd29ybGQgZ3JhcGgiLGxvZz0ieHkiKQ0KcGxvdChkZWdyZWVfZGlzdHJpYnV0aW9uKHNmKSxwY2g9MjAseGxhYj0iayIseWxhYj0iUChrKSIsbGFzPTEsbWFpbj0iU2NhbGUtZnJlZSBncmFwaCIsbG9nPSJ4eSIpDQpwbG90KGRlZ3JlZV9kaXN0cmlidXRpb24obG4pLHBjaD0yMCx4bGFiPSJrIix5bGFiPSJQKGspIixsYXM9MSxtYWluPSJMb2ctbm9ybWFsIGRpc3RyaWJ1dGlvbiIsbG9nPSJ4eSIpDQpgYGANCg0KLS0tDQoNCg0KIyMgcmV3aXJpbmcgYWxnb3JpdGhtDQoNClJld2lyaW5nIGFsZ29yaXRobSB0byB0dW5lIGRlZ3JlZSAoZGlzKWFzc29ydGF0aXZpdHkgW0BOZXdtYW4yMDAyXToNCg0KYGBge3J9DQpmcmV3aXJlX3IgPC0gZnVuY3Rpb24obmV0d29yaywgdGFyZ2V0X3IsIG1heF9pdGVyID0gMWU1LCB0b2wgPSAwLjAxLCB2ZXJib3NlID0gVFJVRSkgew0KICANCiAgY3VycmVudF9yIDwtIGFzc29ydGF0aXZpdHlfZGVncmVlKG5ldHdvcmspDQogIGl0ZXJhdGlvbiA8LSAxDQogIA0KICBpZiAodmVyYm9zZSkgew0KICAgIGNhdCgiVGFyZ2V0IGFzc29ydGF0aXZpdHkgY29lZmZpY2llbnQ6IiwgdGFyZ2V0X3IsICJcbiIpDQogICAgY2F0KCJTdGFydGluZyBhc3NvcnRhdGl2aXR5IGNvZWZmaWNpZW50OiIsIGN1cnJlbnRfciwgIlxuIikNCiAgICBjYXQoIlRvbGVyYW5jZToiLCB0b2wsICJcbiIpDQogIH0NCiAgDQogIHdoaWxlIChhYnMoY3VycmVudF9yIC0gdGFyZ2V0X3IpID4gdG9sICYmIGl0ZXJhdGlvbiA8IG1heF9pdGVyKSB7DQogICAgDQogICAgIyBnZXQgbmV0d29yayBlZGdlcw0KICAgIGVkZ2VzIDwtIEUobmV0d29yaykNCiAgICAjIHRvIGVkZ2VsaXN0DQogICAgZWRnZV9saXN0IDwtIGVuZHMobmV0d29yaywgZWRnZXMpDQogICAgDQogICAgIyByYW5kb21seSBzZWxlY3QgdHdvIHBhaXJzIG9mIGNvbm5lY3RlZCBub2Rlcw0KICAgIGlkeDEgPC0gc2FtcGxlKDE6bnJvdyhlZGdlX2xpc3QpLCAxKQ0KICAgIGlkeDIgPC0gc2FtcGxlKDE6bnJvdyhlZGdlX2xpc3QpLCAxKQ0KICAgIA0KICAgICMgZXh0cmFjdCBub2RlIGluZGljZXMNCiAgICB1MSA8LSBlZGdlX2xpc3RbaWR4MSwgMV0gIyBub2RlIDEgb2YgZmlyc3QgZWRnZQ0KICAgIHYxIDwtIGVkZ2VfbGlzdFtpZHgxLCAyXSAjIG5vZGUgMiBvZiBmaXJzdCBlZGdlDQogICAgdTIgPC0gZWRnZV9saXN0W2lkeDIsIDFdICMgZXRjDQogICAgdjIgPC0gZWRnZV9saXN0W2lkeDIsIDJdIA0KICAgIA0KICAgICMgY2hlY2sgaWYgdGhlIHR3byBwYWlycyBvZiBjb25uZWN0ZWQgbm9kZXMgKHUxLCB2MTsgdTIsIHYyKSBhcmUgZGlzam9pbnQNCiAgICBpZiAobGVuZ3RoKHVuaXF1ZShjKHUxLCB2MSwgdTIsIHYyKSkpID09IDQpIHsNCiAgICAgICMgY2hlY2sgaWYgdGhlcmUgaXMgYWxyZWFkeSBhbiBlZGdlIGFjcm9zcyB0aGUgbm9kZS1wYWlycw0KICAgICAgIyBlbnN1cmUgbm8gbG9vcHMgYW5kIG5vIGR1cGxpY2F0ZSBlZGdlcw0KICAgICAgaWYgKCFhcmVfYWRqYWNlbnQobmV0d29yaywgdTEsIHUyKSAmJiAhYXJlX2FkamFjZW50KG5ldHdvcmssIHYxLCB2MikgJiYgdTEgIT0gdjIgJiYgdTIgIT0gdjEpIHsNCiAgICAgICAgDQogICAgICAgICMgcGVyZm9ybSB0aGUgZWRnZSBzd2FwICh1MSx2MSkgPC0+ICh1Mix2MikgYmVjb21lcyAodTEsdjIpIDwtPiAodTIsdjEpDQogICAgICAgIG5ld19uZXR3b3JrIDwtIG5ldHdvcmsgIyBjb3B5IG5ldHdvcmsNCiAgICAgICAgDQogICAgICAgICMgY2hlY2sgaWYgdGhlIG5ldyBlZGdlcyBhbHJlYWR5IGV4aXN0IHRvIGF2b2lkIGR1cGxpY2F0ZXMNCiAgICAgICAgaWYgKCFhcmVfYWRqYWNlbnQobmV3X25ldHdvcmssIHUxLCB2MikgJiYgIWFyZV9hZGphY2VudChuZXdfbmV0d29yaywgdTIsIHYxKSkgew0KICAgICAgICAgICMgYWRkIGVkZ2VzDQogICAgICAgICAgbmV3X25ldHdvcmsgPC0gYWRkX2VkZ2VzKG5ld19uZXR3b3JrLCBjKHUxLCB2MiwgdTIsIHYxKSkNCiAgICAgICAgICAjIHJlbW92ZSBlZGdlcw0KICAgICAgICAgIG5ld19uZXR3b3JrIDwtIGRlbGV0ZV9lZGdlcyhuZXdfbmV0d29yaywgZ2V0LmVkZ2UuaWRzKG5ld19uZXR3b3JrLCBjKHUxLCB2MSwgdTIsIHYyKSkpDQogICAgICAgICAgDQogICAgICAgICAgIyBuZXcgYXNzb3J0YXRpdml0eQ0KICAgICAgICAgIG5ld19yIDwtIGFzc29ydGF0aXZpdHlfZGVncmVlKG5ld19uZXR3b3JrKQ0KICAgICAgICAgIA0KICAgICAgICAgICMgYWNjZXB0IHRpZSBzd2FwIGlmIGl0IGJyaW5ncyB1cyBjbG9zZXIgdG8gdGhlIHRhcmdldCBhc3NvcnRhdGl2aXR5DQogICAgICAgICAgaWYgKGFicyhuZXdfciAtIHRhcmdldF9yKSA8IGFicyhjdXJyZW50X3IgLSB0YXJnZXRfcikpIHsNCiAgICAgICAgICAgIGN1cnJlbnRfciA8LSBuZXdfcg0KICAgICAgICAgICAgbmV0d29yayA8LSBuZXdfbmV0d29yaw0KICAgICAgICAgICAgaWYgKHZlcmJvc2UpIHsgDQogICAgICAgICAgICAgIGNhdCgiUmV3aXJpbmcgYXQgaXRlcmF0aW9uIiwgaXRlcmF0aW9uLCAiYnJvdWdodCBhc3NvcnRhdGl2aXR5IGNsb3NlciB0byB0YXJnZXQhIEN1cnJlbnQgYXNzb3J0YXRpdml0eSBjb2VmZmljaWVudDoiLCBuZXdfciwgIlxuIikNCiAgICAgICAgICAgIH0NCiAgICAgICAgICB9DQogICAgICAgIH0NCiAgICAgIH0NCiAgICB9DQogICAgaXRlcmF0aW9uIDwtIGl0ZXJhdGlvbiArIDENCiAgfQ0KICANCiAgaWYgKHZlcmJvc2UpIHsNCiAgICBjYXQoIkZpbmFsIGFzc29ydGF0aXZpdHkgY29lZmZpY2llbnQ6IiwgY3VycmVudF9yLCAiXG4iKQ0KICAgIGlmIChhYnMoY3VycmVudF9yIC0gdGFyZ2V0X3IpIDw9IHRvbCkgew0KICAgICAgY2F0KCJUYXJnZXQgcmVhY2hlZCB3aXRoaW4gdG9sZXJhbmNlLlxuIikNCiAgICB9IGVsc2Ugew0KICAgICAgY2F0KCJSZWFjaGVkIG1heGltdW0gaXRlcmF0aW9ucyB3aXRob3V0IG1lZXRpbmcgdGFyZ2V0LlxuIikNCiAgICB9DQogIH0NCiAgDQogIHJldHVybihuZXR3b3JrKQ0KfQ0KYGBgDQoNCjxicj4NCg0KQXBwbHkgdGhlIGZ1bmN0aW9uOg0KDQpgYGB7cn0NCiNpbml0aWFsaXplIHNjYWxlLWZyZWUgbmV0d29yaw0KbmV0d29yayA8LSBzYW1wbGVfZGVnc2VxKGZkZWdzZXEobiA9IG4sIGFscGhhID0gMi43LCBrX21pbiA9IDMsIHNlZWQgPSAxMjMpLCBtZXRob2QgPSAidmwiKQ0KDQojZ2V0IGN1cnJlbnQgYXNzb3J0YXRpdml0eSBjb2VmZmljaWVudA0KYXNzb3J0YXRpdml0eV9kZWdyZWUobmV0d29yaykNCg0KI3NldCBuZXcgdGFyZ2V0cw0KZnJld2lyZV9yKG5ldHdvcmssIHRhcmdldF9yID0gLTAuMiwgbWF4X2l0ZXIgPSAxZTMpDQoNCiMgYWxzbyBmb3IgcmFuZG9tIG5ldHdvcmsgd2l0aCBzaW1pbGFyIGRlbnNpdHkNCmRlbnMgPC0gbWVhbihkZWdyZWUobmV0d29yaykpIC8gKG4tMSkNCg0KbmV0d29yayA8LSBmcmFuZGttaW4obj1uLHA9ZGVucyxrX21pbj0zLCBzZWVkID0gMTIzKQ0KYXNzb3J0YXRpdml0eV9kZWdyZWUobmV0d29yaykNCmZyZXdpcmVfcihuZXR3b3JrLCB0YXJnZXRfciA9IC0wLjIsIG1heF9pdGVyID0gMWUzKQ0KDQpWKG5ldHdvcmspJHJvbGUgPC0gc2FtcGxlKGMocmVwKCJ0cmVuZHNldHRlciIsIGZsb29yKG4qcF90KSksIHJlcCgiY29uZm9ybWlzdCIsIG4gLSBmbG9vcihuKnBfdCkpKSkNCmBgYA0KDQotLS0NCg0KIyMgc3dhcHBpbmcgYWxnb3JpdGhtDQoNClN3YXBwaW5nIGFsZ29yaXRobSB0byB0dW5lIGRlZ3JlZS10cmFpdCBjb3JyZWxhdGlvbiBbQExlcm1hbjIwMTZdOg0KDQpgYGB7cn0NCiMgd2UgbWFuaXB1bGF0ZSBkZWdyZWUtdHJhaXQgY29ycmVsYXRpb24gKHJobykgYnkgc3dhcHBpbmcgYXR0cmlidXRlIHZhbHVlcyBhbW9uZyB0aGUgbm9kZXMuIFRvIGluY3JlYXNlIFxyaG9fe2t4fSwgd2UgcmFuZG9tbHkgY2hvb3NlIG5vZGVzIHZfMSB3aXRoIHg9MSBhbmQgdl8wIHdpdGggeD0wIGFuZCBzd2FwIHRoZWlyIGF0dHJpYnV0ZXMgaWYgdGhlIGRlZ3JlZSBvZiB0aGUgZGVncmVlIG9mIHZfMCBpcyBncmVhdGVyIHRoYW4gdGhhdCBvZiB2XzEgKHVudGlsIHRoZSBkZXNpcmVkIFxyaG9fe2t4fSBpcyByZWFjaGVkOyBvciBpdCBubyBsb25nZXIgY2hhbmdlcykuDQpmZGVndHJhaXRjb3IgPC0gZnVuY3Rpb24obmV0d29yaykgew0KICByb2xlcyA8LSBpZmVsc2UoVihuZXR3b3JrKSRyb2xlID09ICJ0cmVuZHNldHRlciIsIDEsIDApDQogIGRlZ3JlZXMgPC0gZGVncmVlKG5ldHdvcmspDQogIHJldHVybihsaXN0KGNvciA9IGNvcihyb2xlcywgZGVncmVlcyksIHJvbGVzID0gcm9sZXMsIGRlZ3JlZXMgPSBkZWdyZWVzKSkNCn0NCg0KI3N3YXBwaW5nIGZ1bmN0aW9uIHRvIGFkanVzdCBkZWdyZWUtdHJhaXQgY29ycmVsYXRpb24NCmZzd2FwX3JobyA8LSBmdW5jdGlvbihuZXR3b3JrLCB0YXJnZXRfcmhvLCBtYXhfaXRlciA9IDFlMywgdG9sID0gMC4wNSwgdmVyYm9zZSA9IFRSVUUpIHsNCiAgDQogIGN1cnJlbnQgPC0gZmRlZ3RyYWl0Y29yKG5ldHdvcmspDQogIGl0ZXJhdGlvbiA8LSAxDQogIGJlc3RfbmV0d29yayA8LSBuZXR3b3JrDQogIGJlc3RfcmhvIDwtIGN1cnJlbnQkY29yDQogIA0KICBpZiAodmVyYm9zZSkgew0KICAgIGNhdCgiVGFyZ2V0IGRlZ3JlZS10cmFpdCBjb3JyZWxhdGlvbjoiLCB0YXJnZXRfcmhvLCAiXG4iKQ0KICAgIGNhdCgiU3RhcnRpbmcgZGVncmVlLXRyYWl0IGNvcnJlbGF0aW9uOiIsIGN1cnJlbnQkY29yLCAiXG4iKQ0KICAgIGNhdCgiVG9sZXJhbmNlOiIsIHRvbCwgIlxuXG4iKQ0KICB9DQogIA0KICB3aGlsZSAoaXRlcmF0aW9uIDw9IG1heF9pdGVyKSB7DQogICAgIyBjaGVjayBpZiB3ZSBhcmUgYWxyZWFkeSB3aXRoaW4gdG9sZXJhbmNlDQogICAgaWYgKGFicyhjdXJyZW50JGNvciAtIHRhcmdldF9yaG8pIDw9IHRvbCkgew0KICAgICAgaWYgKHZlcmJvc2UpIGNhdCgiVGFyZ2V0IHJlYWNoZWQgd2l0aGluIHRvbGVyYW5jZSBhdCBpdGVyYXRpb24iLCBpdGVyYXRpb24sICIuXG4iKQ0KICAgICAgYnJlYWsNCiAgICB9DQogICAgDQogICAgIyByYW5kb21seSBzZWxlY3Qgbm9kZXMgZm9yIHN3YXBwaW5nDQogICAgdjEgPC0gc2FtcGxlKHdoaWNoKGN1cnJlbnQkcm9sZXMgPT0gMSksIDEpDQogICAgdjAgPC0gc2FtcGxlKHdoaWNoKGN1cnJlbnQkcm9sZXMgPT0gMCksIDEpDQogICAgDQogICAgIyBnZXQgZGVncmVlcyBvZiBzZWxlY3RlZCBub2Rlcw0KICAgIGsxIDwtIGN1cnJlbnQkZGVncmVlc1t2MV0NCiAgICBrMCA8LSBjdXJyZW50JGRlZ3JlZXNbdjBdDQogICAgDQogICAgIyBzd2FwIHJvbGVzIGlmIGNvbmRpdGlvbiBrX3YwID4ga192MSBpcyBtZXQNCiAgICBpZiAoazAgPiBrMSkgew0KICAgICAgY3VycmVudCRyb2xlc1t2MV0gPC0gMA0KICAgICAgY3VycmVudCRyb2xlc1t2MF0gPC0gMQ0KICAgICAgDQogICAgICAjIHVwZGF0ZSBncmFwaCByb2xlcw0KICAgICAgVihuZXR3b3JrKSRyb2xlIDwtIGlmZWxzZShjdXJyZW50JHJvbGVzID09IDEsICJ0cmVuZHNldHRlciIsICJjb25mb3JtaXN0IikNCiAgICAgIA0KICAgICAgIyByZWNhbGN1bGF0ZSBkZWdyZWUtdHJhaXQgY29ycmVsYXRpb24NCiAgICAgIGN1cnJlbnQgPC0gZmRlZ3RyYWl0Y29yKG5ldHdvcmspDQogICAgICANCiAgICAgICMgY2hlY2sgaWYgdGhpcyBpcyB0aGUgY2xvc2VzdCBjb3JyZWxhdGlvbiB0byB0aGUgdGFyZ2V0IHNvIGZhcg0KICAgICAgaWYgKGFicyhjdXJyZW50JGNvciAtIHRhcmdldF9yaG8pIDwgYWJzKGJlc3RfcmhvIC0gdGFyZ2V0X3JobykpIHsNCiAgICAgICAgYmVzdF9uZXR3b3JrIDwtIG5ldHdvcmsNCiAgICAgICAgYmVzdF9yaG8gPC0gY3VycmVudCRjb3INCiAgICAgICAgaWYgKHZlcmJvc2UpIHsNCiAgICAgICAgICBjYXQoIlRyYWl0LXN3YXBwaW5nIGF0IGl0ZXJhdGlvbiIsIGl0ZXJhdGlvbiwgImJyb3VnaHQgY29ycmVsYXRpb24gY2xvc2VyIHRvIHRhcmdldCEgQ3VycmVudCBjb3JyZWxhdGlvbjoiLCBjdXJyZW50JGNvciwgIlxuIikNCiAgICAgICAgfQ0KICAgICAgfQ0KICAgIH0NCiAgICBpdGVyYXRpb24gPC0gaXRlcmF0aW9uICsgMQ0KICB9DQogIA0KICAjIGNoZWNrIGlmIHRoZSBmaW5hbCBjb3JyZWxhdGlvbiBpcyB3b3JzZSB0aGFuIHRoZSBiZXN0IGNvcnJlbGF0aW9uDQogIGZpbmFsX2NvcnJlbGF0aW9uIDwtIGN1cnJlbnQkY29yDQogIGlmIChhYnMoZmluYWxfY29ycmVsYXRpb24gLSB0YXJnZXRfcmhvKSA+IGFicyhiZXN0X3JobyAtIHRhcmdldF9yaG8pKSB7DQogICAgaWYgKHZlcmJvc2UpIHsNCiAgICAgIGNhdCgiXG5XYXJuaW5nOiBGaW5hbCBpdGVyYXRpb24gbWFkZSB0aGUgY29ycmVsYXRpb24gd29yc2UuIFJldmVydGluZyB0byBiZXN0IG9ic2VydmVkIGNvcnJlbGF0aW9uLlxuIikNCiAgICB9DQogIH0NCiAgDQogIGlmICh2ZXJib3NlKSB7DQogICAgY2F0KCJcbkZpbmFsIGRlZ3JlZS10cmFpdCBjb3JyZWxhdGlvbjoiLCBiZXN0X3JobywgIlxuIikNCiAgICBpZiAoYWJzKGJlc3RfcmhvIC0gdGFyZ2V0X3JobykgPD0gdG9sKSB7DQogICAgICBjYXQoIlRhcmdldCByZWFjaGVkIHdpdGhpbiB0b2xlcmFuY2UuXG4iKQ0KICAgIH0gZWxzZSBpZiAoaXRlcmF0aW9uID4gbWF4X2l0ZXIpIHsNCiAgICAgIGNhdCgiUmVhY2hlZCBtYXhpbXVtIGl0ZXJhdGlvbnMgd2l0aG91dCBtZWV0aW5nIHRhcmdldC5cbiIpDQogICAgfQ0KICB9DQogIHJldHVybihiZXN0X25ldHdvcmspDQp9DQpgYGANCg0KPGJyPg0KDQpBcHBseSB0aGUgZnVuY3Rpb246DQoNCmBgYHtyfQ0KI2dldCBjdXJyZW50IGRlZ3JlZS10cmFpdCBjb3JyZWxhdGlvbg0KZmRlZ3RyYWl0Y29yKG5ldHdvcmspDQoNCiNzZXQgbmV3IHRhcmdldA0KdGFyZ2V0ID0gMC41DQoNCmZkZWd0cmFpdGNvcihmc3dhcF9yaG8obmV0d29yayA9IG5ldHdvcmssIHRhcmdldF9yaG8gPSB0YXJnZXQsIHRvbD0wLjAxKSkNCmBgYA0KDQo8YnI+DQoNClNpbXVsYXRlIG5ldHdvcmtzIChoZXJlLCBzY2FsZS1mcmVlIHdpdGggYWxwaGEgPSAyLjcpIHdpdGggaW5kZXBlbmRlbnRseSB2YXJ5aW5nIGRlZ3JlZSBhc3NvcnRhdGl2aXR5IGFuZCBkZWdyZWUtdHJhaXQgY29ycmVsYXRpb246DQoNCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTAsIGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KcGFyKG1mcm93PWMoMywzKSkNCg0KIyBORVRXT1JLUw0KYWxwaGEgPSAyLjcNCmRlZ3NlcSA8LSBmZGVnc2VxKG4gPSA5NiwgYWxwaGEgPSBhbHBoYSwga19taW4gPSAzLCBzZWVkID0gMjM1MikNCm5ldHdvcmsgPC0gc2FtcGxlX2RlZ3NlcShkZWdzZXEsIG1ldGhvZCA9ICJ2bCIpDQpWKG5ldHdvcmspJHJvbGUgPC0gc2FtcGxlKGMocmVwKCJ0cmVuZHNldHRlciIsIGZsb29yKG4qcF90KSksIHJlcCgiY29uZm9ybWlzdCIsIG4gLSBmbG9vcihuKnBfdCkpKSkNCg0KbmV0d29yazEgPC0gZnJld2lyZV9yKG5ldHdvcmssIHRhcmdldF9yID0gLTAuMSwgdmVyYm9zZSA9IEZBTFNFKQ0KDQpmcGxvdF9ncmFwaChuZXR3b3JrMSwNCiAgICAgICAgICAgIG1haW49cGFzdGUwKCJjb25maWd1cmF0aW9uIG1vZGVsXG5kZWdyZWUgc2VxdWVuY2UgZ2VuZXJhdGVkIGZyb20gcChrKX5rXnstzrF9XG5OPTk2OyBwcm9wLiB0cmVuZHNldHRlciA9IDAuMTsgzrE9Mi43OyBrX21pbj0zXG5yX2trID0gIiwgDQogICAgICAgICAgICAgICAgICAgICAgICByb3VuZChhc3NvcnRhdGl2aXR5X2RlZ3JlZShuZXR3b3JrMSksMyksIA0KICAgICAgICAgICAgICAgICAgICAgICAgIjsgcF9reCA9ICIsIHJvdW5kKGZkZWd0cmFpdGNvcihuZXR3b3JrMSkkY29yLDMpKSkNCg0KbmV0d29yazIgPC0gZnJld2lyZV9yKG5ldHdvcmssIHRhcmdldF9yID0gLTAuMywgdmVyYm9zZSA9IEZBTFNFKQ0KDQpmcGxvdF9ncmFwaChuZXR3b3JrMiwNCiAgICAgICAgICAgIG1haW49cGFzdGUwKCJjb25maWd1cmF0aW9uIG1vZGVsXG5kZWdyZWUgc2VxdWVuY2UgZ2VuZXJhdGVkIGZyb20gcChrKX5rXnstzrF9XG5OPTk2OyBwcm9wLiB0cmVuZHNldHRlciA9IDAuMTsgzrE9Mi43OyBrX21pbj0zXG5yX2trID0gIiwgDQogICAgICAgICAgICAgICAgICAgICAgICByb3VuZChhc3NvcnRhdGl2aXR5X2RlZ3JlZShuZXR3b3JrMiksMyksIA0KICAgICAgICAgICAgICAgICAgICAgICAgIjsgcF9reCA9ICIsIHJvdW5kKGZkZWd0cmFpdGNvcihuZXR3b3JrMikkY29yLDMpKSkNCg0KbmV0d29yazMgPC0gZnN3YXBfcmhvKG5ldHdvcmsyLCB0YXJnZXRfcmhvID0gMC41LCB2ZXJib3NlPUZBTFNFKQ0KDQpmcGxvdF9ncmFwaChuZXR3b3JrMywNCiAgICAgICAgICAgIG1haW49cGFzdGUwKCJjb25maWd1cmF0aW9uIG1vZGVsXG5kZWdyZWUgc2VxdWVuY2UgZ2VuZXJhdGVkIGZyb20gcChrKX5rXnstzrF9XG5OPTk2OyBwcm9wLiB0cmVuZHNldHRlciA9IDAuMTsgzrE9Mi43OyBrX21pbj0zXG5yX2trID0gIiwgDQogICAgICAgICAgICAgICAgICAgICAgICByb3VuZChhc3NvcnRhdGl2aXR5X2RlZ3JlZShuZXR3b3JrMyksMyksIA0KICAgICAgICAgICAgICAgICAgICAgICAgIjsgcF9reCA9ICIsIHJvdW5kKGZkZWd0cmFpdGNvcihuZXR3b3JrMykkY29yLDMpKSkNCg0KIyBERUdSRUUgQVNTT1JUQVRJVklUWQ0KDQpwbG90KGRlZ3JlZShuZXR3b3JrKSwga25uKG5ldHdvcmspJGtubiwgcGNoPTE5LCB4bGFiID0gIk5vZGUgZGVncmVlIChrKSIsIHlsYWIgPSAiQXZlcmFnZSBuZWlnaGJvciBkZWdyZWUgKDxrJz4pIiwNCiAgICAgbWFpbiA9ICJkZWdyZWUgYXNzb3J0YXRpdml0eSIpDQpwbG90KGRlZ3JlZShuZXR3b3JrMiksIGtubihuZXR3b3JrMikka25uLCBwY2g9MTksIHhsYWIgPSAiTm9kZSBkZWdyZWUgKGspIiwgeWxhYiA9ICJBdmVyYWdlIG5laWdoYm9yIGRlZ3JlZSAoPGsnPikiLA0KICAgICBtYWluID0gImRlZ3JlZSBhc3NvcnRhdGl2aXR5IikNCnBsb3QoZGVncmVlKG5ldHdvcmszKSwga25uKG5ldHdvcmszKSRrbm4sIHBjaD0xOSwgIHhsYWIgPSAiTm9kZSBkZWdyZWUgKGspIiwgeWxhYiA9ICJBdmVyYWdlIG5laWdoYm9yIGRlZ3JlZSAoPGsnPikiLA0KICAgICBtYWluID0gImRlZ3JlZSBhc3NvcnRhdGl2aXR5IikNCg0KIyBWSU9MSU4gUExPVCBPRiBERUdSRUUgRElTVFJJQlVUSU9OIFBFUiBST0xFDQoNCiMgZXh0cmFjdCBub2RlIGRlZ3JlZXMNCmRlZ3JlZXMgPC0gZGVncmVlKG5ldHdvcmsxKQ0KdHJlbmRzZXR0ZXJfZGVncmVlcyA8LSBkZWdyZWVzW1YobmV0d29yazEpJHJvbGUgPT0gInRyZW5kc2V0dGVyIl0NCmNvbmZvcm1pc3RfZGVncmVlcyA8LSBkZWdyZWVzW1YobmV0d29yazEpJHJvbGUgPT0gImNvbmZvcm1pc3QiXQ0KDQp2aW9wbG90KHRyZW5kc2V0dGVyX2RlZ3JlZXMsIGNvbmZvcm1pc3RfZGVncmVlcywgDQogICAgICAgIG5hbWVzID0gYygiVHJlbmRzZXR0ZXJzIiwgIkNvbmZvcm1pc3RzIiksDQogICAgICAgICBjb2wgPSBjKCAiIzgwMDA4MCIsICIjRkZENzAwIiksDQogICAgICAgIG1haW4gPSAiZGVncmVlIGRpc3RyaWJ1dGlvbiIpDQoNCmRlZ3JlZXMyIDwtIGRlZ3JlZShuZXR3b3JrMikNCnRyZW5kc2V0dGVyX2RlZ3JlZXMyIDwtIGRlZ3JlZXMyW1YobmV0d29yazIpJHJvbGUgPT0gInRyZW5kc2V0dGVyIl0NCmNvbmZvcm1pc3RfZGVncmVlczIgPC0gZGVncmVlczJbVihuZXR3b3JrMikkcm9sZSA9PSAiY29uZm9ybWlzdCJdDQoNCnZpb3Bsb3QodHJlbmRzZXR0ZXJfZGVncmVlczIsIGNvbmZvcm1pc3RfZGVncmVlczIsIA0KICAgICAgICBuYW1lcyA9IGMoIlRyZW5kc2V0dGVycyIsICJDb25mb3JtaXN0cyIpLA0KICAgICAgICAgY29sID0gYyggIiM4MDAwODAiLCAiI0ZGRDcwMCIpLA0KICAgICAgICBtYWluID0gImRlZ3JlZSBkaXN0cmlidXRpb24iKQ0KDQpkZWdyZWVzMyA8LSBkZWdyZWUobmV0d29yazMpDQp0cmVuZHNldHRlcl9kZWdyZWVzMyA8LSBkZWdyZWVzM1tWKG5ldHdvcmszKSRyb2xlID09ICJ0cmVuZHNldHRlciJdDQpjb25mb3JtaXN0X2RlZ3JlZXMzIDwtIGRlZ3JlZXMzW1YobmV0d29yazMpJHJvbGUgPT0gImNvbmZvcm1pc3QiXQ0KdmlvcGxvdCh0cmVuZHNldHRlcl9kZWdyZWVzMywgY29uZm9ybWlzdF9kZWdyZWVzMywgDQogICAgICAgIG5hbWVzID0gYygiVHJlbmRzZXR0ZXJzIiwgIkNvbmZvcm1pc3RzIiksDQogICAgICAgIGNvbCA9IGMoICIjODAwMDgwIiwgIiNGRkQ3MDAiKSwNCiAgICAgICAgbWFpbiA9ICJkZWdyZWUgZGlzdHJpYnV0aW9uIikNCmBgYA0KDQotLS0NCg0KU3BlY2lmeSBhIHBhcmFtZXRlciBzcGFjZSB0byBnZW5lcmF0ZSBuZXR3b3JrcyB3aXRoIGluZGVwZW5kZW50bHkgdmFyaWVkIGRlZ3JlZSBkaXN0cmlidXRpb24sIGRlZ3JlZSBhc3NvcnRhdGl2aXR5LCBkZWdyZWUtdHJhaXQgY29ycmVsYXRpb246DQoNCmBgYHtyfQ0KIyBzdHJ1Y3R1cmFsIHBhcmFtZXRlcnMgDQpuID0gOTYNCmtfbWluID0gMw0KcF90ID0gMC4xDQoNCiMgdGFyZ2V0IHBhcmFtZXRlcnMNCmFscGhhcyA8LSBjKDIuNCwgMi43LCAzKQ0KZGlzdHJpYnV0aW9ucyA8LSBjKCJwb3dlci1sYXciLCAibG9nLW5vcm1hbCIpDQp0YXJnZXRfcl92YWx1ZXMgPC0gbGlzdChzZXEoLTAuNCwgLTAuMTUsIGJ5ID0gMC4wNSksDQogICAgICAgICAgICAgICAgICAgICAgICBzZXEoLTAuMzUsIC0wLjA1LCBieSA9IDAuMDUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgc2VxKC0wLjMsIDAuMSwgYnkgPSAwLjA1KQ0KICAgICAgICAgICAgICAgICAgICAgICAgKQ0KbmFtZXModGFyZ2V0X3JfdmFsdWVzKSA8LSBhbHBoYXMNCnRhcmdldF9yaG9fdmFsdWVzIDwtIHNlcSgwLCAwLjYsIGJ5ID0gMC4xKQ0KDQojIGxpc3QgZm9yIHJlc3VsdHMNCnJlc3VsdHMgPC0gbGlzdCgpDQoNCmZvciAoZGlzdCBpbiBkaXN0cmlidXRpb25zKSB7DQogIGZvciAoYWxwaGEgaW4gYWxwaGFzKSB7DQoNCiAgICAjIGdlbmVyYXRlIGRlZ3JlZSBzZXF1ZW5jZSBmcm9tIHNwZWNpZmllZCBkaXN0cmlidXRpb24gYW5kIGFscGhhDQogICAgZGVnc2VxIDwtIGZkZWdzZXEobiA9IG4sIGRpc3QgPSBkaXN0LCBhbHBoYSA9IGFscGhhLCBrX21pbiA9IGtfbWluLCBzZWVkID0gMTIzKQ0KICAgIA0KICAgICMgY3JlYXRlIGFuIHVuZGlyZWN0ZWQsIGNvbm5lY3RlZCwgc2ltcGxlIGdyYXBoIHVzaW5nIFZpZ2VyLUxhdGFweSBhbGdvcml0aG0NCiAgICBuZXR3b3JrIDwtIHNhbXBsZV9kZWdzZXEoZGVnc2VxLCBtZXRob2QgPSAidmwiKQ0KICAgIA0KICAgICMgYXNzaWduIHJvbGVzIHJhbmRvbWx5LCBiYXNlZCBvbiBwcm9wb3J0aW9uIHRyZW5kc2V0dGVyIHBfdA0KICAgIFYobmV0d29yaykkcm9sZSA8LSBzYW1wbGUoYyhyZXAoInRyZW5kc2V0dGVyIiwgZmxvb3IobipwX3QpKSwgcmVwKCJjb25mb3JtaXN0IiwgbiAtIGZsb29yKG4qcF90KSkpKQ0KICAgIA0KICAgIGZvciAodGFyZ2V0X3IgaW4gdGFyZ2V0X3JfdmFsdWVzW1thcy5jaGFyYWN0ZXIoYWxwaGEpXV0pIHsgI2xvb3Agb3ZlciB0YXJnZXRfciB2YWx1ZXMNCiAgICAgIHJld2lyZWRfbmV0d29yayA8LSBmcmV3aXJlX3IobmV0d29yaywgdGFyZ2V0X3IsIHZlcmJvc2U9RkFMU0UpDQogICAgICBhY3R1YWxfciA8LSBhc3NvcnRhdGl2aXR5X2RlZ3JlZShyZXdpcmVkX25ldHdvcmspDQogICAgICANCiAgICAgIGZvciAodGFyZ2V0X3JobyBpbiB0YXJnZXRfcmhvX3ZhbHVlcykgeyAjbG9vcCBvdmVyIHRhcmdldF9yaG8gdmFsdWVzDQogICAgICAgIGZpbmFsX25ldHdvcmsgPC0gZnN3YXBfcmhvKHJld2lyZWRfbmV0d29yaywgdGFyZ2V0X3JobywgdmVyYm9zZT1GQUxTRSkNCiAgICAgICAgZmluYWxfcmhvIDwtIGZkZWd0cmFpdGNvcihmaW5hbF9uZXR3b3JrKSRjb3INCiAgICAgICAgDQogICAgICAgICMgYW5kIGFzc3VtZSB0aGF0IGFnZW50cyBhY3QgYWNjb3JkaW5nIHRvIHRoZWlyIHJvbGUNCiAgICAgICAgIyBhZ2VudHMgZG9uJ3Qgb2JzZXJ2ZSBlYWNoIG90aGVycycgcm9sZXMsIGJ1dCBkbyBvYnNlcnZlIGFjdGlvbnMgKGJhc2VkIG9uIHdoaWNoIHRoZXkgY2FuIGluZmVyIHJvbGVzL3ByZWZlcmVuY2VzLi4uKQ0KICAgICAgICANCiAgICAgICAgVihmaW5hbF9uZXR3b3JrKSRhY3Rpb24gPC0gaWZlbHNlKFYoZmluYWxfbmV0d29yaykkcm9sZSA9PSAidHJlbmRzZXR0ZXIiLCAxLCAwKQ0KICAgICAgICANCiAgICAgICAgIyBzdG9yZSByZXN1bHRzDQogICAgICAgIHJlc3VsdHMgPC0gYXBwZW5kKHJlc3VsdHMsIGxpc3QobGlzdCgNCiAgICAgICAgICBkaXN0cmlidXRpb24gPSBkaXN0LA0KICAgICAgICAgIGFscGhhID0gYWxwaGEsDQogICAgICAgICAgdGFyZ2V0X3IgPSB0YXJnZXRfciwNCiAgICAgICAgICBhY3R1YWxfciA9IGFjdHVhbF9yLA0KICAgICAgICAgIHRhcmdldF9yaG8gPSB0YXJnZXRfcmhvLA0KICAgICAgICAgIGFjdHVhbF9yaG8gPSBmaW5hbF9yaG8sDQogICAgICAgICAgbmV0d29yayA9IGZpbmFsX25ldHdvcmsNCiAgICAgICAgICApKSkNCiAgICAgIH0NCiAgICB9DQogIH0NCn0NCmBgYA0KDQo8YnI+DQoNClNhbXBsZSBzb21lIHNpbXVsYXRpb25zIGZyb20gdGhlICduZXR3b3JrIHVuaXZlcnNlJzoNCg0KYGBge3J9DQojIGV4Y2x1ZGluZyB0aGUgZ3JhcGggb2JqZWN0Og0KbGFwcGx5KHNhbXBsZShyZXN1bHRzLCA1KSwgZnVuY3Rpb24oeCkgew0KICB6IDwtIHggIyBjb3B5DQogIHokbmV0d29yayA8LSBOVUxMDQogIHoNCn0pDQpgYGANCg0KLS0tDQoNCiMgIm1ham9yaXR5IGlsbHVzaW9uIg0KDQpBIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSB0aGUgbWFnbml0dWRlIG9mIHRoZSAibWFqb3JpdHkgaWxsdXNpb24iLCBiYXNlZCBvbiB0aGUgbmV0d29yayBzdHJ1Y3R1cmUsIGFuZCBhIGdpdmVuIG1ham9yaXR5IHRocmVzaG9sZCAoaGVyZSwgInN0cm9uZyBpbmZsdWVuY2UiLCBzbyBoYWxmIG9mIG5laWdoYm9ycyBuZWVkIHRvIGFkb3B0IHRoZSB0cmVuZCBpbiBvcmRlciBmb3IgYW4gZWdvIHRvIGJlIHBlcnN1YWRlZCB0byBkbyB0aGUgc2FtZSk6DQoNCmBgYHtyfQ0KY2FsY3VsYXRlX21ham9yaXR5X2lsbHVzaW9uIDwtIGZ1bmN0aW9uKG5ldHdvcmssIHRocmVzaG9sZCA9IDAuNDkpIHsNCiAgcm9sZXMgPC0gVihuZXR3b3JrKSRyb2xlDQogIGFjdGlvbnMgPC0gVihuZXR3b3JrKSRhY3Rpb24NCg0KICAjaW5pdGlhbGl6ZSBjb3VudGVyIGZvciBtYWpvcml0eSBpbGx1c2lvbg0KICBtaV9jb3VudCA8LSAwDQogIA0KICAjbG9vcCBvdmVyIGNvbmZvcm1pc3RzDQogIGZvciAodiBpbiBWKG5ldHdvcmspKSB7DQogICAgaWYgKHJvbGVzW3ZdID09ICJjb25mb3JtaXN0Iikgew0KICAgICAgbmVpZ2hib3JzIDwtIG5laWdoYm9ycyhuZXR3b3JrLCB2KQ0KICAgICAgdHJlbmRfbmVpZ2hib3JzIDwtIHN1bShhY3Rpb25zW25laWdoYm9yc10gPT0gMSkNCiAgICAgIHByb3BfdHJlbmQgPC0gdHJlbmRfbmVpZ2hib3JzIC8gbGVuZ3RoKG5laWdoYm9ycykNCiAgICAgIA0KICAgICAgaWYgKHByb3BfdHJlbmQgPiB0aHJlc2hvbGQpIHsgI3VuZGVyICJ3ZWFrIGluZmx1ZW5jZSIsIG1vcmUgdGhhbiBoYWxmIG9mIHRoZSBuZWlnaGJvcmhvb2Qgc3VmZmljZXM7IHNvIHNldCB0aHJlc2hvbGQgYXQgLjUNCiAgICAgICAgbWlfY291bnQgPC0gbWlfY291bnQgKyAxDQogICAgICB9DQogICAgfQ0KICB9DQogICMgcmV0dXJuIGZyYWN0aW9uIG9mIGNvbmZvcm1pc3RzIHdobyBoYXZlIG1ham9yaXR5IGlsbHVzaW9uDQogIHJldHVybihtaV9jb3VudCAvIHN1bShyb2xlcyA9PSAiY29uZm9ybWlzdCIpKQ0KfQ0KYGBgIA0KDQo8YnI+DQoNCkFwcGx5IHRoZSBmdW5jdGlvbiBvdmVyIG91ciAnbmV0d29yayB1bml2ZXJzZScsIHdoZXJlIGVhY2ggbmV0d29yayBjb3JyZXNwb25kcyB0byBhIHNwZWNpZmljIHBhcmFtZXRlciBzcGFjZSByb3csIGdlbmVyYXRlZCB1c2luZyBhIHVuaXF1ZSBzZWVkOg0KDQpgYGB7ciwgZmlnLndpZHRoPTEyLGZpZy5oZWlnaHQ9OH0NCnBsb3RkYXRhIDwtIGRvLmNhbGwocmJpbmQsIGxhcHBseShyZXN1bHRzLCBmdW5jdGlvbihyZXMpIHsNCiAgZGlzdCA8LSBmYWN0b3IocmVzJGRpc3QsIGxldmVscyA9IGMoInBvd2VyLWxhdyIsICJsb2ctbm9ybWFsIikpDQogIGFscGhhIDwtIHJlcyRhbHBoYQ0KICByIDwtIHJlcyRhY3R1YWxfcg0KICByaG8gPC0gcmVzJGFjdHVhbF9yaG8NCiAgbmV0d29yayA8LSByZXMkbmV0d29yaw0KICANCiAgIyBjYWxjdWxhdGUgdGhlIG1ham9yaXR5IGlsbHVzaW9uIChpLmUuLCB0aGUgcHJvcG9ydGlvbiBvZiBjb25mb3JtaXN0cyB3aG9zZSBuZWlnaGJvcnMgbWVldCBvciBleGNlZWQgdGhyZXNob2xkIM+GKQ0KICBtaSA8LSBjYWxjdWxhdGVfbWFqb3JpdHlfaWxsdXNpb24obmV0d29yaykNCiAgDQogICMgY3JlYXRlIGEgZGF0YS1mcmFtZQ0KICBkYXRhLmZyYW1lKGRpc3QgPSBkaXN0LA0KICAgICAgICAgICAgIGFscGhhID0gYWxwaGEsIA0KICAgICAgICAgICAgIHIgPSByLCANCiAgICAgICAgICAgICByaG8gPSByaG8sIA0KICAgICAgICAgICAgIG1pID0gbWkpDQp9KSkNCg0KI21ha2Ugc2VwYXJhdGUgZGF0YS1mcmFtZXMgZm9yIGVhY2ggbGV2ZWwgb2YgYWxwaGENCmFscGhhMSA8LSBwbG90ZGF0YVtwbG90ZGF0YSRhbHBoYSA9PSB1bmlxdWUocGxvdGRhdGEkYWxwaGEpWzFdLCBdDQphbHBoYTIgPC0gcGxvdGRhdGFbcGxvdGRhdGEkYWxwaGEgPT0gdW5pcXVlKHBsb3RkYXRhJGFscGhhKVsyXSwgXQ0KYWxwaGEzIDwtIHBsb3RkYXRhW3Bsb3RkYXRhJGFscGhhID09IHVuaXF1ZShwbG90ZGF0YSRhbHBoYSlbM10sIF0NCg0KI2NyZWF0ZSBiaW5zIGZvciByIChkZWctYXNzb3JhdGl2aXR5KQ0KZmNyZWF0ZV9iaW5zIDwtIGZ1bmN0aW9uKGRhdGEsIHZhcmlhYmxlID0gInIiLCBvdXQgPSA2KSB7DQogIHJ2YWxzIDwtIGRhdGFbW3ZhcmlhYmxlXV0NCiAgcXVhbnQgPC0gcXVhbnRpbGUocnZhbHMsIHByb2JzID0gc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSBvdXQpKSANCiAgIyBnZW5lcmF0ZSBsYWJlbHMgZHluYW1pY2FsbHkNCiAgbGFiZWxzIDwtIHNhcHBseSgxOihsZW5ndGgocXVhbnQpIC0gMSksIGZ1bmN0aW9uKGkpIHsNCiAgICBwYXN0ZTAocm91bmQocXVhbnRbaV0sIDIpLCAiIDwgciDiiaQgIiwgcm91bmQocXVhbnRbaSArIDFdLCAyKSkNCiAgfSkNCiAgIyBhZGQgY2F0ZWdvcmllcyB0byB0aGUgZGF0YXNldA0KICBkYXRhJHJfY2F0cyA8LSBjdXQocnZhbHMsIGJyZWFrcyA9IHF1YW50LCBpbmNsdWRlLmxvd2VzdCA9IFRSVUUsIGxhYmVscyA9IGxhYmVscykNCiAgcmV0dXJuKGRhdGEpDQp9DQoNCiMgYXBwbHkgYmlubmluZyAgdG8gZWFjaCBzdWJzZXQNCmFscGhhMSA8LSBmY3JlYXRlX2JpbnMoYWxwaGExKQ0KYWxwaGEyIDwtIGZjcmVhdGVfYmlucyhhbHBoYTIpDQphbHBoYTMgPC0gZmNyZWF0ZV9iaW5zKGFscGhhMykNCg0KcGxvdDEgPC0gZ2dwbG90KGFscGhhMSwgYWVzKHggPSByaG8sIHkgPSBtaSwgY29sb3IgPSBhcy5mYWN0b3Iocl9jYXRzKSkpICsNCiAgZmFjZXRfd3JhcCh+ZGlzdCwgbnJvdyA9IDIpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNywgc2l6ZSA9IDIpICsNCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSwgbWV0aG9kID0gImxvZXNzIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygtMC4wNSwgMC43KSkgKw0KICBsYWJzKA0KICAgIHggPSBleHByZXNzaW9uKCJkZWdyZWUtdHJhaXQgY29ycmVsYXRpb24gKCIgfiByaG9ba3hdIH4gIikiKSwgDQogICAgeSA9ICJwcm9wLiBjb25mb3JtaXN0cyB3LyBwcm9wLiB0cmVuZHNldHRlciBuYmguID4gz4YiLCAgICAgIA0KICAgIGNvbG9yID0gZXhwcmVzc2lvbigiZGVncmVlIGFzc29ydGF0aXZpdHkgKCIgfiByW2trXSB+ICIpIikNCiAgKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC4zLCAwLjM1KSkgKyBnZ3RpdGxlKHBhc3RlMCgizrE9IiwgYWxwaGExJGFscGhhKSkNCg0KcGxvdDIgPC0gZ2dwbG90KGFscGhhMiwgYWVzKHggPSByaG8sIHkgPSBtaSwgY29sb3IgPSBhcy5mYWN0b3Iocl9jYXRzKSkpICsNCiAgZmFjZXRfd3JhcCh+ZGlzdCwgbnJvdyA9IDIpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNywgc2l6ZSA9IDIpICsNCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSwgbWV0aG9kID0gImxvZXNzIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygtMC4wNSwgMC43KSkgKw0KICBsYWJzKA0KICAgIHggPSBleHByZXNzaW9uKCJkZWdyZWUtdHJhaXQgY29ycmVsYXRpb24gKCIgfiByaG9ba3hdIH4gIikiKSwgDQogICAgeSA9ICJwcm9wLiBjb25mb3JtaXN0cyB3LyBwcm9wLiB0cmVuZHNldHRlciBuYmguID4gz4YiLCAgICAgIA0KICAgIGNvbG9yID0gZXhwcmVzc2lvbigiZGVncmVlIGFzc29ydGF0aXZpdHkgKCIgfiByW2trXSB+ICIpIikNCiAgKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC4zLCAwLjM1KSkgKyBnZ3RpdGxlKHBhc3RlMCgizrE9IiwgYWxwaGEyJGFscGhhKSkNCg0KcGxvdDMgPC0gZ2dwbG90KGFscGhhMywgYWVzKHggPSByaG8sIHkgPSBtaSwgY29sb3IgPSBhcy5mYWN0b3Iocl9jYXRzKSkpICsNCiAgZmFjZXRfd3JhcCh+ZGlzdCwgbnJvdyA9IDIpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNywgc2l6ZSA9IDIpICsNCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSwgbWV0aG9kID0gImxvZXNzIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygtMC4wNSwgMC43KSkgKw0KICBsYWJzKA0KICAgIHggPSBleHByZXNzaW9uKCJkZWdyZWUtdHJhaXQgY29ycmVsYXRpb24gKCIgfiByaG9ba3hdIH4gIikiKSwgDQogICAgeSA9ICJwcm9wLiBjb25mb3JtaXN0cyB3LyBwcm9wLiB0cmVuZHNldHRlciBuYmguID4gz4YiLCAgICAgIA0KICAgIGNvbG9yID0gZXhwcmVzc2lvbigiZGVncmVlIGFzc29ydGF0aXZpdHkgKCIgfiByW2trXSB+ICIpIikNCiAgKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC4zLCAwLjM1KSkgKyBnZ3RpdGxlKHBhc3RlMCgizrE9IiwgYWxwaGEzJGFscGhhKSkNCg0KI2NvbWJpbmUNCmdnYXJyYW5nZShwbG90MSxwbG90MixwbG90MyxuY29sPTMpICsNCiAgZ2d0aXRsZSgnICJNYWpvcml0eSBpbGx1c2lvbiIgaW4gbmV0d29ya3Mgd2l0aCBhIHBvd2VyLWxhdyBhbmQgbG9nLW5vcm1hbCBkZWdyZWUgZGlzdHJpYnV0aW9uJyApICsgdGhlbWUoDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE2KQ0KICApDQpgYGAgDQoNCjxicj4NCg0KTm93IGZvciBlYWNoIHBhcmFtZXRlciBzcGFjZSByb3cgZ2VuZXJhdGUgbmV0d29ya3Mgb3ZlciBOIGRpZmZlcmVudCBzZWVkczoNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpuID0gOTYgIyBudW1iZXIgb2YgYWdlbnRzDQpwX3RfdmFsdWVzIDwtIGMoMC4wNSwgMC4xLCAwLjIpICMgcHJvcG9ydGlvbiAidHJlbmRzZXR0ZXJzIg0Ka19taW4gPSAzDQojIHRhcmdldCBwYXJhbWV0ZXJzDQphbHBoYXMgPC0gYygyLjQsIDIuNywgMykNCmRpc3RyaWJ1dGlvbnMgPC0gYygicG93ZXItbGF3IiwgImxvZy1ub3JtYWwiLCAiYmlub21pYWwiKQ0KdGFyZ2V0X3JfdmFsdWVzIDwtIGxpc3Qoc2VxKC0wLjQsIC0wLjE1LCBieSA9IDAuMDUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgc2VxKC0wLjM1LCAtMC4wNSwgYnkgPSAwLjA1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHNlcSgtMC4zLCAwLjEsIGJ5ID0gMC4wNSkNCiAgICAgICAgICAgICAgICAgICAgICAgICkNCm5hbWVzKHRhcmdldF9yX3ZhbHVlcykgPC0gYWxwaGFzDQp0YXJnZXRfcmhvX3ZhbHVlcyA8LSBzZXEoMCwgMC42LCBieSA9IDAuMSkNCg0KIyBzZXQgdXAgcGFyYWxsZWwgYmFja2VuZCB0byBpbmNyZWFzZSBlZmZpY2llbmN5DQpuY29yZXMgPC0gZGV0ZWN0Q29yZXMoKSAtIDEgDQpjbCA8LSBtYWtlQ2x1c3RlcihuY29yZXMpDQpyZWdpc3RlckRvUGFyYWxsZWwoY2wpDQoNCiMgbnVtYmVyIG9mIHNlZWRzDQpuSXRlciA8LSA1MA0KDQojIGxpc3QgdG8gc3RvcmUgcmVzdWx0cyBpbg0KcmVwX3Jlc3VsdHMgPC0gbGlzdCgpDQoNCiMgcGFyYWxsZWwgcHJvY2Vzc2luZyB1c2luZyBmb3JlYWNoDQpyZXBfcmVzdWx0cyA8LSBmb3JlYWNoKGFscGhhID0gYWxwaGFzLCAuY29tYmluZSA9ICdjJywgLnBhY2thZ2VzID0gYygiaWdyYXBoIikpICU6JQ0KICBmb3JlYWNoKHBfdCA9IHBfdF92YWx1ZXMsIC5jb21iaW5lID0gJ2MnLCAucGFja2FnZXMgPSBjKCJpZ3JhcGgiKSkgJTolDQogIGZvcmVhY2goaXRlciA9IDE6bkl0ZXIsIC5jb21iaW5lID0gJ2MnLCAucGFja2FnZXMgPSBjKCJpZ3JhcGgiKSkgJWRvcGFyJSB7DQogICAgDQogICAgIyBzZWVkIG11c3QgdmFyeSB3aXRoIGVhY2ggaXRlcmF0aW9uDQogICAgc2VlZCA8LSAxMjMgKyBpdGVyDQogICAgc2V0LnNlZWQoc2VlZCkNCiAgDQogICAgcHJpbnQocGFzdGUwKCJSdW5uaW5nIGl0ZXJhdGlvbiAiLCBpdGVyLCAiLyIsIG5JdGVyLCAiIGZvciBhbHBoYSA9ICIsIGFscGhhLCAiLCBwX3QgPSAiLCBwX3QpKQ0KICAgIA0KICAgIHJlc3VsdHMgPC0gbGlzdCgpICAjdGVtcG9yYXJ5IHN0b3JhZ2UgZm9yIHRoZSByZXN1bHRzDQogICAgDQogICAgIyBsb29wIG92ZXIgZGlzdHJpYnV0aW9uIHR5cGVzOg0KICAgIGZvciAoZGlzdCBpbiBkaXN0cmlidXRpb25zKSB7DQogICAgICANCiAgICAgIGlmIChkaXN0ID09ICJiaW5vbWlhbCIpIHsgIyBpZiByYW5kb20gbmV0d29yazsgZmlyc3QgZ2V0IGEgdGFyZ2V0IDxrPiBmcm9tIHRoZSBjb3JyZXNwb25kaW5nIHBvd2VyLWxhdyBuZXR3b3JrDQogICAgICAgIGRlZ3NlcSA8LSBmZGVnc2VxKG4gPSBuLCBhbHBoYSA9IGFscGhhLCBrX21pbiA9IGtfbWluLCBzZWVkID0gMTIzICsgaXRlciwgZGlzdCA9ICJwb3dlci1sYXciKQ0KICAgICAgICBkZW5zIDwtIG1lYW4oZGVnc2VxKS8obi0xKQ0KICAgICAgICANCiAgICAgICAgIyBnZW5lcmF0ZSByYW5kb20gbmV0d29yaywgbWVldGluZyBrX21pbiwgd2l0aCAnZml4ZWQnIGRlbnNpdHkNCiAgICAgICAgbmV0d29yayA8LSBmcmFuZGttaW4obj1uLCBwPWRlbnMsIGtfbWluPTMsIHZlcmJvc2U9RkFMU0UsIHNlZWQgPSAxMjMgKyBpdGVyKQ0KICAgICAgfSBlbHNlIHsNCiAgICAgICAgDQogICAgICAgICMgZ2VuZXJhdGUgZGVncmVlIHNlcXVlbmNlIHdpdGggc3BlY2lmaWVkIGRpc3RyaWJ1dGlvbiB0eXBlDQogICAgICAgIGRlZ3NlcSA8LSBmZGVnc2VxKG4gPSBuLCBhbHBoYSA9IGFscGhhLCBrX21pbiA9IGtfbWluLCBzZWVkID0gMTIzICsgaXRlciwgZGlzdCA9IGRpc3QpDQogICAgICANCiAgICAgICAgIyBjb25zdHJ1Y3QgdGhlIG5ldHdvcmsNCiAgICAgICAgbmV0d29yayA8LSBzYW1wbGVfZGVnc2VxKGRlZ3NlcSwgbWV0aG9kID0gInZsIikNCiAgICAgICAgDQogICAgICB9DQogICAgICAjIGFzc2lnbiByb2xlcyByYW5kb21seQ0KICAgICAgVihuZXR3b3JrKSRyb2xlIDwtIHNhbXBsZShjKHJlcCgidHJlbmRzZXR0ZXIiLCBmbG9vcihuICogcF90KSksIHJlcCgiY29uZm9ybWlzdCIsIG4gLSBmbG9vcihuICogcF90KSkpKQ0KICAgICAgDQogICAgICAjIGxvb3Agb3ZlciB0YXJnZXRfciB2YWx1ZXMNCiAgICAgIGZvciAodGFyZ2V0X3IgaW4gdGFyZ2V0X3JfdmFsdWVzW1thcy5jaGFyYWN0ZXIoYWxwaGEpXV0pIHsNCiAgICAgICAgcmV3aXJlZF9uZXR3b3JrIDwtIGZyZXdpcmVfcihuZXR3b3JrLCB0YXJnZXRfciwgdmVyYm9zZSA9IEZBTFNFLCBtYXhfaXRlciA9IDFlNCkNCiAgICAgICAgYWN0dWFsX3IgPC0gYXNzb3J0YXRpdml0eV9kZWdyZWUocmV3aXJlZF9uZXR3b3JrKQ0KICAgICAgICANCiAgICAgICAgIyBsb29wIG92ZXIgdGFyZ2V0X3JobyB2YWx1ZXMNCiAgICAgICAgZm9yICh0YXJnZXRfcmhvIGluIHRhcmdldF9yaG9fdmFsdWVzKSB7DQogICAgICAgICAgZmluYWxfbmV0d29yayA8LSBmc3dhcF9yaG8ocmV3aXJlZF9uZXR3b3JrLCB0YXJnZXRfcmhvLCB2ZXJib3NlID0gRkFMU0UpDQogICAgICAgICAgZmluYWxfcmhvIDwtIGZkZWd0cmFpdGNvcihmaW5hbF9uZXR3b3JrKSRjb3INCiAgICAgICAgICANCiAgICAgICAgICAjIHVwZGF0ZSBjaG9pY2VzIChjaG9pY2UgPT0gcm9sZSk6IA0KICAgICAgICAgIFYoZmluYWxfbmV0d29yaykkYWN0aW9uIDwtIGlmZWxzZShWKGZpbmFsX25ldHdvcmspJHJvbGUgPT0gInRyZW5kc2V0dGVyIiwgMSwgMCkNCiAgICAgICAgICANCiAgICAgICAgICAjIGNhbGN1bGF0ZSBnbG9iYWwgbWFqb3JpdHkgaWxsdXNpb24NCiAgICAgICAgICBtaSA8LSBjYWxjdWxhdGVfbWFqb3JpdHlfaWxsdXNpb24oZmluYWxfbmV0d29yaykNCiAgICAgICAgICANCiAgICAgICAgICAjIGFwcGVuZCByZXN1bHRzDQogICAgICAgICAgcmVzdWx0cyA8LSBhcHBlbmQocmVzdWx0cywgbGlzdChsaXN0KA0KICAgICAgICAgICAgcF90ID0gcF90LA0KICAgICAgICAgICAgZGlzdCA9IGRpc3QsDQogICAgICAgICAgICBhbHBoYSA9IGFscGhhLA0KICAgICAgICAgICAgdGFyZ2V0X3IgPSB0YXJnZXRfciwNCiAgICAgICAgICAgIGFjdHVhbF9yID0gYWN0dWFsX3IsDQogICAgICAgICAgICB0YXJnZXRfcmhvID0gdGFyZ2V0X3JobywNCiAgICAgICAgICAgIGFjdHVhbF9yaG8gPSBmaW5hbF9yaG8sDQogICAgICAgICAgICBtaSA9IG1pLA0KICAgICAgICAgICAgc2VlZCA9IHNlZWQsICMgc3RvcmUgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5DQogICAgICAgICAgICBuZXR3b3JrID0gZmluYWxfbmV0d29yayAjIHN0b3JlIG5ldHdvcmtzLCBzbyB3ZSBjYW4gYWNjZXNzIHRoZXNlIGZvciB0aGUgc2ltdWxhdGlvbnMuDQogICAgICAgICAgKSkpDQogICAgICAgIH0NCiAgICAgIH0NCiAgICB9DQogICAgcmVzdWx0cw0KICB9DQoNCnN0b3BDbHVzdGVyKGNsKQ0KDQojc2F2ZSBvdXRwdXQuLg0KZnNhdmUocmVwX3Jlc3VsdHMsICJuZXR3b3Jrcy5SZGEiKQ0KYGBgDQoNCjxicj4NCg0KVmlzdWFsaXplIHRoZSBleHRlbnQgb2YgbWFqb3JpdHkgaWxsdXNpb24gZGVwZW5kaW5nIG9uIHRoZSBuZXR3b3JrIHBhcmFtZXRlcnMgYWNyb3NzIHR3byBkZWdyZWUgZGlzdHJpYnV0aW9uIHR5cGVzLCBiYXNlZCBvbiBuZXR3b3JrIHNpbXVsYXRpb25zIHdpdGggZGlmZmVyZW50IHNlZWRzLiBWaXN1YWxpemF0aW9ucyBpbmNsdWRlIGJvdGggb2JzZXJ2ZWQgTUkgYW5kIHByZWRpY3RlZCB2YWx1ZXMgZnJvbSBPTFMgcmVncmVzc2lvbi4NCg0KYGBge3J9DQpuZXRzIDwtIGZsb2FkKCIuL2RhdGEvcHJvY2Vzc2VkLzIwMjUwMjA3bmV0d29ya3MuUmRhIikNCg0KIyB0byBhIGRhdGFmcmFtZToNCmRmIDwtIGRvLmNhbGwocmJpbmQsIGxhcHBseShuZXRzLCBmdW5jdGlvbihyZXMpIHsNCiAgZGF0YS5mcmFtZSgNCiAgICBzZWVkID0gcmVzJHNlZWQsDQogICAgcF90ID0gcmVzJHBfdCwNCiAgICBkaXN0ID0gcmVzJGRpc3QsDQogICAgYWxwaGEgPSByZXMkYWxwaGEsDQogICAgdGFyZ2V0X3IgPSByZXMkdGFyZ2V0X3IsDQogICAgYWN0dWFsX3IgPSByZXMkYWN0dWFsX3IsDQogICAgdGFyZ2V0X3JobyA9IHJlcyR0YXJnZXRfcmhvLA0KICAgIGFjdHVhbF9yaG8gPSByZXMkYWN0dWFsX3JobywNCiAgICBtaSA9IHJlcyRtaQ0KICApDQp9KSkNCg0KIyBjcmVhdGUgYSAzRCBzY2F0dGVycGxvdCBvZiBvYnNlcnZlZCB2YWx1ZXMgb2YgTUksIGRpc2FnZ3JlZ2F0ZWQgYnkNCiMgZGVncmVlIGRpc3RyaWJ1dGlvbiB0eXBlDQoNCiMgb25seSBuZXR3b3JrcyB3aXRoIHBfdD0xMA0KZGYyIDwtIGRmW2RmJHBfdD09LjEsIF0NCg0KI0BSRjogYnV0IGhlcmUgd2UgY2FuIHR3ZWFrIHBfdCAoc2hvd2luZyB0aGF0IGluIHJhbmRvbSBuZXR3b3JrcyBNSSBhbHNvIG9jY3VycyB3aGVuIHBfdCBpcyBzdWZmaWNpZW50bHkgbGFyZ2UuKQ0KDQoNCmZpZzEgPC0gcGxvdF9seSgpDQoNCmZvciAoZGlzdCBpbiB1bmlxdWUoZGYyJGRpc3QpKSB7DQogIGZpZzEgPC0gZmlnMSAlPiUNCiAgICBhZGRfdHJhY2UoDQogICAgICBkYXRhID0gZGYyW2RmMiRkaXN0ID09IGRpc3QsIF0sDQogICAgICB4ID0gfmFscGhhLA0KICAgICAgeSA9IH5hY3R1YWxfcmhvLA0KICAgICAgeiA9IH5hY3R1YWxfciwNCiAgICAgIGNvbG9yID0gfm1pLA0KICAgICAgdHlwZSA9ICdzY2F0dGVyM2QnLA0KICAgICAgbW9kZSA9ICdtYXJrZXJzJywNCiAgICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IDMpLA0KICAgICAgbmFtZSA9IGFzLmNoYXJhY3RlcihkaXN0KSwNCiAgICAgIGhvdmVydGVtcGxhdGUgPSBwYXN0ZSgNCiAgICAgICAgIs6xOiAle3h9PGJyPiIsDQogICAgICAgICLPgV97eGt9OiAle3l9PGJyPiIsDQogICAgICAgICJyX3tra306ICV7en08YnI+IiwNCiAgICAgICAgIk1JOiAle21hcmtlci5jb2xvcn08ZXh0cmE+PC9leHRyYT4iDQogICAgICApDQogICAgKSANCn0NCg0KIyBhZGQgbGF5b3V0IGRldGFpbHM6DQpmaWcxIDwtIGZpZzEgJT4lDQogIGxheW91dCgNCiAgICB0aXRsZSA9ICczRCBzY2F0dGVycGxvdCBvZiB0aGUgbWFnbml0dWRlIG9mIHRoZSAibWFqb3JpdHkgaWxsdXNpb24iIGJ5IGRlZ3JlZSBkaXN0cmlidXRpb24gdHlwZScsDQogICAgc2NlbmUgPSBsaXN0KA0KICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIs6xIiksDQogICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiz4Ffe3hrfSIpLA0KICAgICAgemF4aXMgPSBsaXN0KHRpdGxlID0gInJfe2trfSIpDQogICAgKQ0KICApICU+JQ0KICBjb2xvcmJhcih0aXRsZT0iTWFqb3JpdHkgaWxsdXNpb24iKQ0KDQoNCiNmaXQgbW9kZWxzID0+IHByZWRpY3RlZCB2YWx1ZXMNCm0xIDwtIGxtKG1pIH4gYWN0dWFsX3JobyArIGFjdHVhbF9yICsgYXMuZmFjdG9yKGFscGhhKSwgZGF0YSA9IGRmMltkZjIkZGlzdCA9PSAicG93ZXItbGF3IixdKQ0KbTIgPC0gbG0obWkgfiBhY3R1YWxfcmhvICsgYWN0dWFsX3IgKyBhcy5mYWN0b3IoYWxwaGEpICsgYWN0dWFsX3JobzphY3R1YWxfciwgZGF0YSA9IGRmMltkZjIkZGlzdCA9PSAicG93ZXItbGF3IixdKQ0KbTMgPC0gbG0obWkgfiBhY3R1YWxfcmhvICsgYWN0dWFsX3IgKyBhcy5mYWN0b3IoYWxwaGEpICsgYWN0dWFsX3JobzphY3R1YWxfciArIGFjdHVhbF9yaG86YXMuZmFjdG9yKGFscGhhKSArIGFjdHVhbF9yOmFzLmZhY3RvcihhbHBoYSksIGRhdGEgPSBkZjJbZGYyJGRpc3QgPT0gInBvd2VyLWxhdyIsXSkNCg0KI2Fub3ZhKG0xLG0yLG0zKQ0KI3N1bW1hcnkobTMpDQoNCiMgY3JlYXRlIGEgc2VxdWVuY2Ugb2YgdmFsdWVzIGZvciByaG8sIHIsIGFuZCBhbHBoYQ0KcmhvX3ZhbHMgPC0gc2VxKG1pbihkZjJbZGYyJGRpc3QgPT0gInBvd2VyLWxhdyIsXSRhY3R1YWxfcmhvKSwgbWF4KGRmMltkZjIkZGlzdCA9PSAicG93ZXItbGF3IixdJGFjdHVhbF9yaG8pLCBsZW5ndGgub3V0ID0gNTApDQpyX3ZhbHMgPC0gc2VxKG1pbihkZjJbZGYyJGRpc3QgPT0gInBvd2VyLWxhdyIsXSRhY3R1YWxfciksIG1heChkZjJbZGYyJGRpc3QgPT0gInBvd2VyLWxhdyIsXSRhY3R1YWxfciksIGxlbmd0aC5vdXQgPSA1MCkNCiNhbHBoYV92YWxzIDwtIHNlcSgyLjQsIDMsIGJ5PTAuMSkgIA0KYWxwaGFfdmFscyA8LSBjKDIuNCwyLjcsMykNCg0KIyBmdW5jdGlvbiB0byBnZW5lcmF0ZSB0aGUgc3VyZmFjZSBkYXRhIGZvciBhIGdpdmVuIGFscGhhIHZhbHVlDQpmc3VyZmFjZWRhdCA8LSBmdW5jdGlvbihhbHBoYV92YWwpIHsNCiAgIyBjcmVhdGUgYSBncmlkIG9mIHIgYW5kIHJobyB2YWx1ZXMNCiAgZ3JpZCA8LSBleHBhbmQuZ3JpZChhY3R1YWxfciA9IHJfdmFscywgYWN0dWFsX3JobyA9IHJob192YWxzKQ0KICANCiAgIyBmaXhlZCBhbHBoYSB2YWx1ZQ0KICBncmlkJGFscGhhIDwtIGFscGhhX3ZhbA0KICANCiAgIyBwcmVkaWN0IHRoZSB2YWx1ZXMgb2YgbWkgdXNpbmcgdGhlIG1vZGVsDQogIGdyaWQkbWlfcHJlZCA8LSBwcmVkaWN0KG0zLCBuZXdkYXRhID0gZ3JpZCkNCiAgDQogICMgcmVzaGFwZSB0aGUgcHJlZGljdGVkIHZhbHVlcyBpbnRvIGEgbWF0cml4DQogIG1pX21hdHJpeCA8LSBtYXRyaXgoZ3JpZCRtaV9wcmVkLCBucm93ID0gbGVuZ3RoKHJfdmFscyksIG5jb2wgPSBsZW5ndGgocmhvX3ZhbHMpLCBieXJvdyA9IFRSVUUpDQogIA0KICByZXR1cm4obWlfbWF0cml4KQ0KfQ0KDQojIHByZWNvbXB1dGUgdGhlIHN1cmZhY2UgZGF0YSBmb3IgYWxsIGFscGhhIHZhbHVlcw0Kc3VyZmFjZV9kYXRhX2xpc3QgPC0gbGFwcGx5KGFscGhhX3ZhbHMsIGZzdXJmYWNlZGF0KQ0KDQojIGZpbmQgdGhlIGdsb2JhbCByYW5nZSBvZiBNTSAoeiB2YWx1ZXMpDQp6X21pbiA8LSBtaW4oc2FwcGx5KHN1cmZhY2VfZGF0YV9saXN0LCBtaW4pKQ0Kel9tYXggPC0gbWF4KHNhcHBseShzdXJmYWNlX2RhdGFfbGlzdCwgbWF4KSkNCg0KIyBjcmVhdGUgdGhlIGluaXRpYWwgc3VyZmFjZSBwbG90IHdpdGggdGhlIGZpcnN0IGFscGhhIHZhbHVlDQpmaWcyIDwtIHBsb3RfbHkoDQogIHggPSByX3ZhbHMsICAjIHgtYXhpczogcg0KICB5ID0gcmhvX3ZhbHMsICAjIHktYXhpczogcmhvDQogIHogPSBzdXJmYWNlX2RhdGFfbGlzdFtbMV1dLCAgIyBzdXJmYWNlIGRhdGEgZm9yIHRoZSBmaXJzdCBhbHBoYQ0KICB0eXBlID0gInN1cmZhY2UiLA0KICBjb2xvcmJhciA9IGxpc3QoDQogICAgdGl0bGUgPSAiTUkiLA0KICAgIGNtaW4gPSB6X21pbiwgDQogICAgY21heCA9IHpfbWF4DQogICksDQogIGNtaW4gPSB6X21pbiwgIA0KICBjbWF4ID0gel9tYXgsDQogIGhvdmVydGVtcGxhdGUgPSBwYXN0ZSgNCiAgICAicl97a2t9OiAle3g6LjJmfTxicj4iLCAgDQogICAgIs+BX3t4a306ICV7eTouMmZ9PGJyPiIsICANCiAgICAiTUk6ICV7ejouMmZ9PGV4dHJhPjwvZXh0cmE+Ig0KICApDQopDQoNCiMgYWRkIHNsaWRlciBmb3IgZHluYW1pYyBhbHBoYSBzZWxlY3Rpb24NCmZpZzIgPC0gZmlnMiAlPiUNCiAgbGF5b3V0KA0KICAgIHRpdGxlID0gcGFzdGUoJ1ByZWRpY3RlZCAibWFqb3JpdHkgaWxsdXNpb24iIGluIHNjYWxlLWZyZWUgbmV0d29yayBmcm9tIE9MUyBtb2RlbCBmb3IgzrEgPScsIGFscGhhX3ZhbHNbMV0pLA0KICAgIHNjZW5lID0gbGlzdCgNCiAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJyX3tra30iKSwNCiAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICLPgV97a3h9IiksDQogICAgICB6YXhpcyA9IGxpc3QoDQogICAgICAgIHRpdGxlID0gIk1JIiwNCiAgICAgICAgcmFuZ2UgPSBjKHpfbWluLCB6X21heCkgDQogICAgICApDQogICAgKSwNCiAgICBzbGlkZXJzID0gbGlzdCgNCiAgICAgIGxpc3QoDQogICAgICAgIGFjdGl2ZSA9IDAsICAjIHN0YXJ0IHdpdGggdGhlIGZpcnN0IGFscGhhIHZhbHVlDQogICAgICAgIHN0ZXBzID0gbGFwcGx5KHNlcV9hbG9uZyhhbHBoYV92YWxzKSwgZnVuY3Rpb24oaSkgew0KICAgICAgICAgIGxpc3QoDQogICAgICAgICAgICBtZXRob2QgPSAidXBkYXRlIiwNCiAgICAgICAgICAgIGFyZ3MgPSBsaXN0KA0KICAgICAgICAgICAgICBsaXN0KA0KICAgICAgICAgICAgICAgIHogPSBsaXN0KHN1cmZhY2VfZGF0YV9saXN0W1tpXV0pICAjdXBkYXRlIHN1cmZhY2UgZGF0YQ0KICAgICAgICAgICAgICApLA0KICAgICAgICAgICAgICBsaXN0KA0KICAgICAgICAgICAgICAgIHRpdGxlID0gcGFzdGUoJ1ByZWRpY3RlZCAibWFqb3JpdHkgaWxsdXNpb24iIGluIHNjYWxlLWZyZWUgbmV0d29yayBmcm9tIE9MUyBtb2RlbCBmb3IgzrEgPScsIGFscGhhX3ZhbHNbaV0pICAjIHVwZGF0ZSB0aXRsZQ0KICAgICAgICAgICAgICApDQogICAgICAgICAgICApLA0KICAgICAgICAgICAgbGFiZWwgPSBhcy5jaGFyYWN0ZXIoYWxwaGFfdmFsc1tpXSkgICMgc2xpZGVyIGxhYmVsDQogICAgICAgICAgKQ0KICAgICAgICB9KSwNCiAgICAgICAgY3VycmVudHZhbHVlID0gbGlzdCgNCiAgICAgICAgICBwcmVmaXggPSAizrE6ICIsICAjIHRleHQgZGlzcGxheWVkIG5leHQgdG8gdGhlIHNsaWRlcg0KICAgICAgICAgIGZvbnQgPSBsaXN0KHNpemUgPSAxNikNCiAgICAgICAgKQ0KICAgICAgKQ0KICAgICkNCiAgKSAgJT4lDQogIGNvbG9yYmFyKHRpdGxlPSJNYWpvcml0eSBpbGx1c2lvbiIpDQoNCmZpZzENCmZpZzINCmBgYCANCg0KLS0tDQoNCiMgc2ltdWxhdGlvbiBmdW5jdGlvbg0KDQpOb3cgdGhhdCB3ZSBoYXZlIG91ciAic3RhcnRpbmcgbmV0d29ya3MiLCBsZXQncyBzaW11bGF0ZSB0aGUgZXZvbHV0aW9uIG9mIGFuIHVucG9wdWxhciBub3JtIHVzaW5nIG91ciB1dGlsaXR5IGZ1bmN0aW9uOg0KDQpgYGB7cn0NCmZhYm0gPC0gZnVuY3Rpb24obmV0d29yayA9IG5ldHdvcmssICMgdGhlIGdlbmVyYXRlZCBuZXR3b3JrDQogICAgICAgICAgICAgICAgIHJvdW5kcyA9IDEwLCAjIG51bWJlciBvZiB0aW1lc3RlcHMvcm91bmRzDQogICAgICAgICAgICAgICAgIGNob2ljZV9ydWxlID0gImRldGVybWluaXN0aWMiLCAjIGNob2ljZSB1cGRhdGUgcnVsZQ0KICAgICAgICAgICAgICAgICB1dGlsaXR5X2ZuID0gZnV0aWxpdHksICMgdGhlIHV0aWxpdHkgZnVuY3Rpb24NCiAgICAgICAgICAgICAgICAgcGFyYW1zID0gbGlzdChzPTE1LCBlPTEwLCB3PTQwLCB6PTUwLCBsYW1iZGExPTQuMywgbGFtZGEyPTEuOCksICMgdXRpbGl0eSBwYXJhbWV0ZXJzDQogICAgICAgICAgICAgICAgIG1pX3RocmVzaG9sZCA9IGlmZWxzZShwYXJhbXMkeiA+IDUwLCAuNDksIC41MCksICMgdW5kZXIgdGhlICJzdHJvbmcgaW5mbHVlbmNlIiBjb25kaXRpb24gKGkuZS4sIDUwPHo8OTApIGhhbGYgb2YgbmVpZ2hib3JzIGFkb3B0aW5nIHRoZSB0cmVuZCBpcyBzdWZmaWNpZW50OyB1bmRlciB0aGUgIndlYWsgaW5mbHVlbmNlIiBjb25kaXRpb24gKGkuZS4sIDMwPHo8NTApICptb3JlKiB0aGFuIGhhbGYgb2YgbmVpZ2hib3JzIGFkb3B0aW5nIHRoZSB0cmVuZCBpcyBuZWVkZWQgdG8gYmUgaW5mbHVlbmNlZC4pDQogICAgICAgICAgICAgICAgIGhpc3RvcmllcyA9IEZBTFNFLCAjIHJldHVybiBkZWNpc2lvbiBoaXN0b3J5DQogICAgICAgICAgICAgICAgIG91dGNvbWUgPSBUUlVFLCAjIHJldHVybiBvdXRjb21lcw0KICAgICAgICAgICAgICAgICBwbG90ID0gRkFMU0UgKSB7ICMgcmV0dXJuIHBsb3QNCiAgDQogICMgbWFrZSBhbiBhZ2VudHMgZGF0YWZyYW1lDQogIGFnZW50cyA8LSB0aWJibGUoDQogICAgaWQgPSAxOmxlbmd0aChuZXR3b3JrKSwNCiAgICByb2xlID0gVihuZXR3b3JrKSRyb2xlLA0KICAgIHByZWZlcmVuY2UgPSBpZmVsc2Uocm9sZSA9PSAidHJlbmRzZXR0ZXIiLCAxLCAwKSwgIyAxID0gZm9sbG93IHRyZW5kLCAwID0gbm90IGZvbGxvdw0KICAgIGNob2ljZSA9IE5BDQogICkNCiAgDQogICMgaW5pdGlhbGl6ZSBkZWNpc2lvbiBoaXN0b3J5DQogIGRlY2lzaW9uX2hpc3RvcnkgPC0gdGliYmxlKCkNCiAgDQogICMgYWxzbyBpbml0aWFsaXplIGFuIGVxdWlsaWJyaXVtIGZsYWcNCiAgZXF1aWxpYnJpdW1fcmVhY2hlZCA8LSBGQUxTRQ0KICBlcXVpbGlicml1bV90IDwtIE5BDQogIA0KICAjIHNpbXVsYXRpb24gbG9vcA0KICBmb3IgKHQgaW4gMTpyb3VuZHMpIHsNCiAgICBpZiAodCA9PSAxKSB7DQogICAgICAjIHJvdW5kIDE6IGFnZW50cyBtYWtlIGRlY2lzaW9ucyBiYXNlZCBvbiBwcml2YXRlIHByZWZlcmVuY2VzIChubyBzb2NpYWwgaW5mb3JtYXRpb24gYXZhaWxhYmxlIHlldCkNCiAgICAgIGFnZW50cyA8LSBhZ2VudHMgJT4lDQogICAgICAgIG11dGF0ZShjaG9pY2UgPSBwcmVmZXJlbmNlKQ0KICAgIH0gZWxzZSB7DQogICAgICAgIyByb3VuZCB0ID4gMTogYWdlbnRzIG1ha2UgZGVjaXNpb25zIGJhc2VkIG9uIHNvY2lhbCBpbmZvcm1hdGlvbiBmcm9tIG5laWdoYm9ycyAoZGVjaXNpb25zIGF0IHQtMSkNCiAgICAgIGFnZW50cyA8LSBhZ2VudHMgJT4lDQogICAgICAgIHJvd3dpc2UoKSAlPiUNCiAgICAgICAgbXV0YXRlKA0KICAgICAgICAgIHV0aWxfMSA9IHV0aWxpdHlfZm4oaWQsIDEsIGFnZW50cywgbmV0d29yaywgbGlzdChzID0gcGFyYW1zJHMsIGUgPSBwYXJhbXMkZSwgdyA9IHBhcmFtcyR3LCB6ID0gcGFyYW1zJHosIGxhbWJkYTEgPSBwYXJhbXMkbGFtYmRhMSwgbGFtYmRhMiA9IHBhcmFtcyRsYW1iZGEyKSkkdXRpbGl0eSwNCiAgICAgICAgICB1dGlsXzAgPSB1dGlsaXR5X2ZuKGlkLCAwLCBhZ2VudHMsIG5ldHdvcmssIGxpc3QocyA9IHBhcmFtcyRzLCBlID0gcGFyYW1zJGUsIHcgPSBwYXJhbXMkdywgeiA9IHBhcmFtcyR6LCBsYW1iZGExID0gcGFyYW1zJGxhbWJkYTEsIGxhbWJkYTIgPSBwYXJhbXMkbGFtYmRhMikpJHV0aWxpdHksDQogICAgICAgICAgIyBtYWtlIGRlY2lzaW9uIGJhc2VkIG9uIGV4cGVjdGVkIHV0aWxpdHkNCiAgICAgICAgICBjaG9pY2UgPSBpZmVsc2UoDQogICAgICAgICAgICBjaG9pY2VfcnVsZSA9PSAiZGV0ZXJtaW5pc3RpYyIsDQogICAgICAgICAgICBpZmVsc2UodXRpbF8xID4gdXRpbF8wLCAxLCAwKSwgICMgZGV0ZXJtaW5pc3RpYyBydWxlDQogICAgICAgICAgICBzYW1wbGUoYygxLCAwKSwgc2l6ZSA9IDEsIHByb2IgPSBjKGV4cCh1dGlsXzEpLCBleHAodXRpbF8wKSkpICAjIHByb2JhYmlsaXN0aWMgcnVsZSAoQFJGOiBvbmx5IGZvciBjb25mb3JtaXN0cz8gbGV2ZWwgb2Ygbm9pc2U/KQ0KICAgICAgICAgICkNCiAgICAgICAgKQ0KICAgIH0NCiAgICAjIHN0b3JlIHRoZSBkZWNpc2lvbnMgZm9yIHRoaXMgcm91bmQNCiAgICBkZWNpc2lvbl9oaXN0b3J5IDwtIGJpbmRfcm93cyhkZWNpc2lvbl9oaXN0b3J5LCBhZ2VudHMgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKHJvdW5kID0gdCkpDQogIH0NCiAgDQogICMgY2hlY2sgZm9yIGVxdWlsaWJyaXVtDQogICMgQFJGOiB3aGVuIGludHJvZHVjaW5nIHNvbWUgc3RvY2hhc3RpY2l0eSwgdGhpcyBzaG91bGQgYmUgaGFuZGxlZCBkaWZmZXJlbnRseS4uLg0KICBmb3IodCBpbiAyOnJvdW5kcyApIHsgDQogICAgcHJldl90cmVuZF9mb2xsb3dlcnMgPC0gbWVhbihkZWNpc2lvbl9oaXN0b3J5ICU+JSBmaWx0ZXIocm91bmQgPT0gdCAtIDEpICU+JSBwdWxsKGNob2ljZSkgPT0gMSkNCiAgICBjdXJyX3RyZW5kX2ZvbGxvd2VycyA8LSBtZWFuKGRlY2lzaW9uX2hpc3RvcnkgJT4lIGZpbHRlcihyb3VuZCA9PSB0KSAlPiUgcHVsbChjaG9pY2UpID09IDEpDQogICAgICAgIA0KICAgIGlmICghZXF1aWxpYnJpdW1fcmVhY2hlZCAmJiBhYnMoY3Vycl90cmVuZF9mb2xsb3dlcnMgLSBwcmV2X3RyZW5kX2ZvbGxvd2VycykgPCAuTWFjaGluZSRkb3VibGUuZXBzKSB7DQogICAgICAgIGVxdWlsaWJyaXVtX3JlYWNoZWQgPC0gVFJVRQ0KICAgICAgICBlcXVpbGlicml1bV90IDwtIHQgLSAxICANCiAgICAgIH0NCiAgICB9DQoNCiAgIyBiYXNlZCBvbiBkZWNpc2lvbl9oaXN0b3J5Og0KICANCiAgIyAxLiBjYWxjdWxhdGUgZ2xvYmFsIG1ham9yaXR5IGlsbHVzaW9uIG92ZXIgcm91bmRzDQogIGdsb2JNSSA8LSBudW1lcmljKHJvdW5kcykNCiAgZm9yICh0IGluIDE6cm91bmRzKSB7DQogICAgaWYgKHQgPT0gMSkgew0KICAgICAgIyBpbiByb3VuZCAxOiBubyBzb2NpYWwgaW5mb3JtYXRpb24sIHNvIG5vIE1JDQogICAgICBnbG9iTUlbdF0gPC0gTkENCiAgICB9IGVsc2Ugew0KICAgICAgIyByb3VuZHMgdCA+IDE6IGNhbGN1bGF0ZSBtYWduaXR1ZGUgb2YgbWFqb3JpdHkgaWxsdXNpb24NCiAgICAgICMgZmlyc3QsIG1ha2UgYSBjb3B5IG9mIHRoZSBuZXR3b3JrIG9iamVjdA0KICAgICAgZXhwb3N1cmVfbmV0d29yayA8LSBuZXR3b3JrDQogICAgICAjIHVwZGF0ZSB0aGUgYWN0aW9ucyBvZiBhY3RvcnMsIGJhc2VkIG9uIHRoZWlyIGNob2ljZXMgaW4gdGhlIHByZXZpb3VzIHJvdW5kDQogICAgICAjIGFmdGVyIGFsbCwgYWN0b3JzIGRvbid0IG9ic2VydmUgb3RoZXJzJyByb2xlcywgYnV0IG9ubHkgdGhlaXIgY2hvaWNlcywNCiAgICAgICMgYmFzZWQgb24gd2hpY2ggdGhleSBjYW4gaW5mZXIgdGhlaXIgcm9sZQ0KICAgICAgVihleHBvc3VyZV9uZXR3b3JrKSRhY3Rpb24gPC0gZGVjaXNpb25faGlzdG9yeVtkZWNpc2lvbl9oaXN0b3J5JHJvdW5kID09IHQgLSAxLF0kY2hvaWNlDQogICAgICBnbG9iTUlbdF0gPC0gY2FsY3VsYXRlX21ham9yaXR5X2lsbHVzaW9uKGV4cG9zdXJlX25ldHdvcmssIHRocmVzaG9sZCA9IG1pX3RocmVzaG9sZCkgIA0KICAgIH0NCiAgfQ0KICANCiAgIyB0byBsb25nIGZvcm1hdA0KICBNSSA8LSB0aWJibGUoDQogICAgcm91bmQgPSAxOnJvdW5kcywNCiAgICBvdXRjb21lID0gIm1ham9yaXR5X2lsbHVzaW9uIiwNCiAgICBzdGF0aXN0aWMgPSBnbG9iTUkNCiAgICApDQogIA0KICAjIDIuIGNhbGN1bGF0ZSB0aGUgZXZvbHV0aW9uIG9mIHRoZSB1bnBvcHVsYXIgbm9ybQ0KICBVTiA8LSBkZWNpc2lvbl9oaXN0b3J5ICU+JQ0KICAgIGdyb3VwX2J5KHJvdW5kKSAlPiUNCiAgICBzdW1tYXJpc2UoDQogICAgICBmb2xsb3dfdHJlbmQgPSBtZWFuKGNob2ljZSA9PSAxLCBuYS5ybSA9IFRSVUUpDQogICAgICApICU+JQ0KICAgICAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKCJmb2xsb3dfdHJlbmQiKSwNCiAgICAgICAgICAgICAgICAgICBuYW1lc190byA9ICJvdXRjb21lIiwNCiAgICAgICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAic3RhdGlzdGljIikNCg0KICAjIGJpbmQNCiAgcGxvdGRhdGEgPC0gYmluZF9yb3dzKE1JLCBVTikNCiAgDQogIGlmIChwbG90KSB7DQogICAgZmlnIDwtIGdncGxvdChwbG90ZGF0YSwgYWVzKHg9cm91bmQsIHk9c3RhdGlzdGljLCBjb2xvcj1mYWN0b3Iob3V0Y29tZSkpKSArDQogICAgICBnZW9tX2xpbmUoKSArDQogICAgICBnZW9tX3BvaW50KCkgKw0KICAgICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoc2NhbGUgPSAxMDApLCBsaW1pdHMgPSBjKDAsMSkpICsNCiAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMSwgbWF4KHBsb3RkYXRhJHJvdW5kKSwgYnkgPSAxKSkgKw0KICAgICBsYWJzKA0KICAgICAgIHRpdGxlID0gIkV2b2x1dGlvbiBvZiBhbiB1bnBvcHVsYXIgbm9ybSIsDQogICAgICAgc3VidGl0bGUgPSAiYGZvbGxvd190cmVuZGAgZGVub3RlcyB0aGUgcGVyY2VudGFnZSBvZiBhbGwgYWdlbnRzIHRoYXQgZm9sbG93IHRoZSB0cmVuZC5cbmBtYWpvcml0eV9pbGx1c2lvbmAgcmVmbGVjdHMgdGhlIHBlcmNlbnRhZ2Ugb2YgY29uZm9ybWlzdHMgd2hvc2UgbmVpZ2hib3JzIG1lZXQgb3IgZXhjZWVkIHRoZSBhZG9wdGlvbiB0aHJlc2hvbGQgz4YgKGkuZS4sIDAuNTApLFxud2l0aCAnc3Ryb25nIGluZmx1ZW5jZScgcmVmZXJyaW5nIHRvIGJvdGggbWVldGluZyBhbmQgZXhjZWVkaW5nIHRoZSB0aHJlc2hvbGQsIGFuZCAnd2VhayBpbmZsdWVuY2UnIHRvIGV4Y2VlZGluZyBpdCBvbmx5LlxuVGhlIGdyZXkgZGFzaGVkIGxpbmUgcmVmbGVjdHMgdGhlIHBlcmNlbnRhZ2Ugb2YgYWdlbnRzIHdob3NlIHJvbGUgaXMgdHJlbmRzZXR0ZXIuIFRoZSBwdXJwbGUgY2lyY2xlIGRlcGljdHMgdGhlIGVxdWlsaWJyaXVtIHBvaW50LiIsDQogICAgICAgeCA9ICJyb3VuZCIsDQogICAgICAgeSA9ICIlIGFnZW50cyIsDQogICAgICAgY29sb3IgPSAib3V0Y29tZSIpICsNCiAgICAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgI2xlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLA0KICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpICsNCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBwcm9wLnRhYmxlKHRhYmxlKGFnZW50cyRyb2xlKSlbMl0sIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImRhcmtncmV5Iiwgc2l6ZSA9IDEpDQogICAgDQogICAgIyBhZGQgYSBjaXJjbGUgYXJvdW5kIHRoZSBlcXVpbGlicml1bSBzdGF0ZSAnZm9sbG93X3RyZW5kJyBzdGF0aXN0aWMNCiAgIGlmICghaXMubmEoZXF1aWxpYnJpdW1fdCkpIHsNCiAgICBmaWcgPC0gZmlnICsgZ2VvbV9wb2ludCgNCiAgICAgIGRhdGEgPSBwbG90ZGF0YSAlPiUgZmlsdGVyKHJvdW5kID09IGVxdWlsaWJyaXVtX3QgJiBvdXRjb21lID09ICJmb2xsb3dfdHJlbmQiKSwNCiAgICAgIGFlcyh4ID0gcm91bmQsIHkgPSBzdGF0aXN0aWMpLCANCiAgICAgIHNoYXBlID0gMSwgc2l6ZSA9IDQsIGNvbG9yID0gInB1cnBsZSIsIHN0cm9rZSA9IDINCiAgICApDQogIH0NCn0NCiAgDQogICMgcmV0dXJuIG91dHB1dHMNCiAgb3V0cHV0IDwtIGxpc3QoKQ0KICBpZiAoaGlzdG9yaWVzKSB7IA0KICAgIG91dHB1dCRkZWNpc2lvbl9oaXN0b3J5IDwtIGRlY2lzaW9uX2hpc3Rvcnl9DQogIA0KICBpZiAob3V0Y29tZSkgeyANCiAgICBvdXRwdXQkb3V0Y29tZXMgPC0gcGxvdGRhdGENCiAgICBvdXRwdXQkZXF1aWxpYnJpdW0gPC0gbGlzdCgNCiAgICAgIHJlYWNoZWQgPSBlcXVpbGlicml1bV9yZWFjaGVkLA0KICAgICAgcm91bmQgPSBlcXVpbGlicml1bV90LA0KICAgICAgcHJvcF9mb2xsb3dfdHJlbmQgPSBpZiAoIWlzLm5hKGVxdWlsaWJyaXVtX3QpKSB7DQogICAgICAgIG1lYW4oZGVjaXNpb25faGlzdG9yeSAlPiUgZmlsdGVyKHJvdW5kID09IGVxdWlsaWJyaXVtX3QpICU+JSBwdWxsKGNob2ljZSkgPT0gMSkNCiAgICAgIH0gZWxzZSB7IE5BIH0NCiAgICApDQogICAgfQ0KICBpZiAocGxvdCkgeyBvdXRwdXQkcGxvdCA8LSBmaWd9DQogIHJldHVybihvdXRwdXQpDQp9DQoNCiN0ZXN0IDwtIGZhYm0obmV0d29yaz1uZXRzW1s0OV1dJG5ldHdvcmssIHJvdW5kcz0xMCwgcGFyYW1zPWxpc3Qocz0xMCwgZT0xMCwgdz0zMCwgej00MCksIGhpc3RvcmllcyA9IFRSVUUsIHBsb3QgPSBUUlVFKQ0KI3Rlc3QkcGxvdA0KDQp0ZXN0IDwtIGZhYm0obmV0d29yaz1uZXRzW1tzYW1wbGUobGVuZ3RoKG5ldHMpLDEpXV0kbmV0d29yaywgcm91bmRzPTEwLCBwYXJhbXM9bGlzdChzPTE1LCBlPTEwLCB3PTQwLCB6PTUwLCBsYW1iZGExPTQuMywgbGFtYmRhMj0xLjgpLCBoaXN0b3JpZXMgPSBUUlVFLCBwbG90ID0gVFJVRSkNCnRlc3QkcGxvdA0KYGBgDQoNCg0KPGJyPg0KDQojIyBleGFtcGxlDQoNClNlbGVjdCByYW5kb20gbmV0d29yayBmcm9tIHRoZSBuZXR3b3JrcyB3aXRoIHRoZSB0b3AgMTAlIGhpZ2hlc3QgbWFqb3JpdHkgaWxsdXNpb24gYW5kIHNpbXVsYXRlIHRoZSBldm9sdXRpb24gb2YgdGhlIHRyZW5kOg0KDQpgYGB7ciwgZmlnLndpZHRoPTgsIGV2YWw9RkFMU0V9DQpzZXQuc2VlZCgyMzU0MzUpICMgc2V0IHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eSANCg0KbWlzIDwtIHNhcHBseShuZXRzLCBmdW5jdGlvbih4KSB4JG1pKSAjIGV4dHJhY3QgTUkgdmFsdWVzDQpzb3J0ZWQgPC0gb3JkZXIobWlzKSAjIHNvcnQgaW5kaWNlcyBieSBNSQ0KbiA8LSBsZW5ndGgobWlzKQ0KDQp0b3AxMCA8LSBzb3J0ZWRbY2VpbGluZygwLjkgKiBuKTpuXSAjIHRvcCAxMCUgTUkNCmluZCA8LSBzYW1wbGUodG9wMTAsIDEwKSAjIHNlbGVjdCByYW5kb20gZWxlbWVudHMNCg0KIyBsb29wIHRocm91Z2ggZWFjaCBzZWxlY3Rpb24gbmV0d29yazsNCiMgaW5zcGVjdCB0aGUgZXZvbHV0aW9uIG9mIE1JIGFuZCBVUA0KZm9yIChpIGluIGluZCkgew0KICAjIGFjY2VzcyBuZXR3b3JrIGFuZCBzdGF0aXN0aWNzDQogIG5ldHdvcmsgPC0gbmV0c1tbaV1dJG5ldHdvcmsNCiAgZGlzdCA8LSBuZXRzW1tpXV0kZGlzdA0KICBhbHBoYSA8LSBuZXRzW1tpXV0kYWxwaGENCiAgYWN0dWFsX3IgPC0gbmV0c1tbaV1dJGFjdHVhbF9yDQogIGFjdHVhbF9yaG8gPC0gbmV0c1tbaV1dJGFjdHVhbF9yaG8NCiAgbWkgPC0gbmV0c1tbaV1dJG1pDQogIA0KICAjIHByaW50IHN0YXRpc3RpY3MNCiAgY2F0KCJTZWxlY3RlZCBuZXR3b3JrIiwgaSwgIlxuIikNCiAgY2F0KCJEaXN0cmlidXRpb246IiwgZGlzdCwgIlxuIikNCiAgaWYoZGlzdCA9PSAicG93ZXItbGF3Iil7Y2F0KCLOsToiLCBhbHBoYSwgIlxuIil9DQogIGNhdCgiPGs+OiIsIG1lYW4oZGVncmVlKG5ldHdvcmspKSwgIlxuIikNCiAgY2F0KCJyX3tra306IiwgYWN0dWFsX3IsICJcbiIpDQogIGNhdCgiz4Ffe2t4fToiLCBhY3R1YWxfcmhvLCAiXG4iKQ0KICBjYXQoIlN0YXJ0aW5nIE1JOiIsIG1pLCAiXG4iKQ0KDQogIGNhdCgnUnVubmluZyBzaW11bGF0aW9uIC4uLlxuJykNCiAgcHJpbnQoZmFibShuZXR3b3JrPW5ldHdvcmssIHJvdW5kcz0yMCwgcGFyYW1zPWxpc3Qocz0xNSwgZT0xMCwgdz00MCwgej01MCwgbGFtYmRhMT00LjMsIGxhbWJkYTI9MS44KSwgcGxvdCA9IFRSVUUpJHBsb3QpDQp9DQpgYGAgDQoNCi0tLQ0KDQojIHNpbXVsYXRpb25zDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KI2NsZWFuIG91ciB3b3JraW5nIGVudmlyb25lbWVudCwgYnV0IGtlZXAgb3VyIGZ1bmN0aW9ucywgb3VyIGRhdGFmcmFtZSwgYW5kIG91ciBsaXN0IG9mIHN0YXJ0aW5nIG5ldHdvcmtzDQpybShsaXN0ID0gc2V0ZGlmZiggbHMoKSwgYygiZGYiLCAibmV0cyIsIGxzZi5zdHIoKSkpICkNCmdjKCkNCg0KIyBAUkY6IGxldCdzIHN0YXJ0IG5vdCB3aXRoIGFsbCBuZXR3b3Jrcy4uLiBidXQgc2VsZWN0IGZvciBlYWNoIHBhcmFtZXRlciBzcGFjZSByb3cgYSBmZXcgc2VlZHMNCiNzdWJfbmV0cyA8LSBGaWx0ZXIoZnVuY3Rpb24oeCkgeCRzZWVkICVpbiUgYygxMjQ6MTMzKSwgbmV0cykNCg0KIyBwYXJhbWV0ZXJzDQpyb3VuZHMgPSAyMA0KDQojIHBhcmFsbGVsIGJhY2tlbmQNCm5jb3JlcyA8LSBkZXRlY3RDb3JlcygpIC0gMiANCmNsIDwtIG1ha2VDbHVzdGVyKG5jb3JlcykNCnJlZ2lzdGVyRG9QYXJhbGxlbChjbCkNCg0KIyMgQFJGOiBpbiBjYXNlIHlvdSBzdG9wIHRoZSBzaW11bGF0aW9uOyBqdXN0IHJldHVybiBmcm9tIHdoZXJlIHlvdSBsZWZ0Og0Kc3RhcnQgPC0gMQ0Kc3RhcnQgPC0gbWF4KGFzLm51bWVyaWMoZ3N1YigiLipfKFxcZCspXFwucmRzIiwgIlxcMSIsIGxpc3QuZmlsZXMoIi4vc2ltcyIpKSkpICsgMQ0KDQppZiAoIWRpci5leGlzdHMoIi4vc2ltcyIpKSBkaXIuY3JlYXRlKCIuL3NpbXMiKQ0KDQojIGV4cG9ydCBjdXN0b20gZnVuY3Rpb25zIGFuZCByZXF1aXJlZCBvYmplY3RzDQpjbHVzdGVyRXhwb3J0KGNsLCBjKCJmYWJtIiwgImZ1dGlsaXR5IiwgIm5ldHMiLCAicm91bmRzIiwgInN0YXJ0IikpDQoNCnN5c3RlbS50aW1lKHsNCiAgZm9yZWFjaChpID0gc3RhcnQ6bGVuZ3RoKG5ldHMpLCAucGFja2FnZXMgPSBjKCJpZ3JhcGgiLCAidGlkeXZlcnNlIikpICVkb3BhciUgew0KICAgICMgc2ltdWxhdGlvbg0KICAgIHNpbSA8LSBmYWJtKA0KICAgICAgbmV0d29yayA9IG5ldHNbW2ldXSRuZXR3b3JrLA0KICAgICAgcm91bmRzID0gcm91bmRzLA0KICAgICAgcGFyYW1zID0gbGlzdChzPTE1LCBlPTEwLCB3PTQwLCB6PTUwLCBsYW1iZGExPTQuMywgbGFtYmRhMj0xLjgpKSRlcXVpbGlicml1bQ0KICAgIA0KICAgICMgZGF0YSBmcmFtZSBmb3IgdGhpcyBuZXR3b3JrDQogICAgc2ltX3Jlc3VsdHMgPC0gZGF0YS5mcmFtZSgNCiAgICAgIHNlZWQgPSBpLA0KICAgICAgZXF1aWxpYnJpdW0gPSBzaW0kcmVhY2hlZCwNCiAgICAgIGVxdWlsaWJyaXVtX3QgPSBzaW0kcm91bmQsDQogICAgICBwcm9wX3RyZW5kID0gc2ltJHByb3BfZm9sbG93X3RyZW5kDQogICAgKQ0KICAgIA0KICAgICMgc2F2ZSBpbnRlcm1lZGlhdGUgcmVzdWx0cw0KICAgIHNhdmVSRFMoc2ltX3Jlc3VsdHMsIGZpbGUgPSBwYXN0ZTAoIi4vc2ltcy9zaW1fbmV0d29ya18iLCBpLCAiLnJkcyIpKSAjDQogICAgDQogICAgICMgcGVyaW9kaWMgbWVtb3J5IGNsZWFudXANCiAgICAgIGlmIChpICUlIDIwMCA9PSAwKSB7DQogICAgICAgIGdjKCkNCiAgICAgIH0NCiAgICANCiAgfQ0KfSkNCiAgDQpzdG9wQ2x1c3RlcihjbCkNCmNsb3NlQWxsQ29ubmVjdGlvbnMoKQ0KYGBgIA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCiMgbGlzdCBhbGwgLnJkcyBmaWxlcyBpbiB0aGUgZm9sZGVyDQpmaWxlX3BhdGhzIDwtIGxpc3QuZmlsZXMoIi4vc2ltcy8iLCBmdWxsLm5hbWVzID0gVFJVRSkNCg0KIyBleHRyYWN0IHRoZSBudW1lcmljIHBhcnQgb2YgdGhlIGZpbGUgbmFtZXMgYW5kIG9yZGVyIHRoZW0gbnVtZXJpY2FsbHkNCmZpbGVfcGF0aHMgPC0gZmlsZV9wYXRoc1tvcmRlcihhcy5udW1lcmljKGdzdWIoIlxcRCIsICIiLCBiYXNlbmFtZShmaWxlX3BhdGhzKSkpKV0NCg0KIyB1c2UgbGFwcGx5IHRvIHJlYWQgYWxsIC5yZHMgZmlsZXMgYW5kIHJiaW5kbGlzdCB0byBjb21iaW5lIHRoZW0NCnN5c3RlbS50aW1lKGFsbF9kYXRhIDwtIGRhdGEudGFibGU6OnJiaW5kbGlzdChsYXBwbHkoZmlsZV9wYXRocywgcmVhZFJEUykpKSAjDQoNCiMgbWFrZSBzdXJlIHdlIG9ubHkgaW5jbHVkZSBuZXR3b3JrcyBmb3Igd2hpY2ggd2UgaGF2ZSBhIHNpbXVsYXRpb24NCiMgZXh0cmFjdCBudW1lcmljIHBhcnQgb2YgZmlsZSBuYW1lcw0KZmlsZV9udW1iZXJzIDwtIGFzLm51bWVyaWMoZ3N1YigiXFxEIiwgIiIsIGJhc2VuYW1lKGZpbGVfcGF0aHMpKSkNCg0Kc29ydGVkX2luZGljZXMgPC0gb3JkZXIoZmlsZV9udW1iZXJzKQ0KZmlsZV9wYXRocyA8LSBmaWxlX3BhdGhzW3NvcnRlZF9pbmRpY2VzXQ0KZmlsZV9udW1iZXJzIDwtIGZpbGVfbnVtYmVyc1tzb3J0ZWRfaW5kaWNlc10NCg0Kc3ViZGYgPC0gZGZbZmlsZV9udW1iZXJzLF0NCg0Kc2ltcyA8LSBjYmluZChzdWJkZiwgYWxsX2RhdGEpDQoNCiNyZW1vdmUgdGhvc2Ugd2l0aCBtaXNzaW5nIG9uIG91dGNvbWUNCm1pcyA8LSB3aGljaChpcy5uYShzaW1zJHByb3BfdHJlbmQpKQ0Kc2ltcyA8LSBzaW1zWy1taXMsIF0NCg0KZnNhdmUoc2ltcywgInNpbV9kYXRhZnJhbWUuUmRhIikNCmBgYA0KDQpgYGB7cn0NCmRhdGEgPC0gZmxvYWQoIi4vZGF0YS9wcm9jZXNzZWQvMjAyNTAyMTJzaW1fZGF0YWZyYW1lLlJkYSIpDQpuYW1lcyhkYXRhKVsxMF0gPC0gImkiDQoNCmhpc3Rfb2JqIDwtIGhpc3QoZGF0YSRwcm9wX3RyZW5kW2RhdGEkcF90PT0wLjEwXSwgYnJlYWtzID0gc2VxKDAsIDEsIGJ5ID0gMC4wNSksIHBsb3QgPSBGQUxTRSkNCmNvbG9ycyA8LSBpZmVsc2UoaGlzdF9vYmokbWlkcyA8IDAuMTUsICJza3libHVlIiwgICAgIA0KICAgICAgICAgICAgICAgICBpZmVsc2UoaGlzdF9vYmokbWlkcyA8IDAuOTUsICJzYWxtb24iLCAgDQogICAgICAgICAgICAgICAgICAgICAgICAicGFsZWdyZWVuIikpDQoNCnBhcihtYXIgPSBjKDUsIDQsIDQsIDYpKQ0KcGxvdChoaXN0X29iaiwgDQogICAgIG1haW4gPSAiSGlzdG9ncmFtIG9mIHByb3BvcnRpb24gdHJlbmQgZm9sbG93ZXJzIGF0IGVxdWlsaWJyaXVtIiwNCiAgICAgc3ViID0gIigxMCUgdHJlbmRzZXR0ZXJzKSIsDQogICAgIHhsYWIgPSAiUHJvcC4gdHJlbmQgZm9sbG93ZXJzIiwgDQogICAgIHhsaW0gPSBjKDAsIDEpLCANCiAgICAgY29sID0gY29sb3JzLA0KICAgICBib3JkZXIgPSAiYmxhY2siLCANCiAgICAgZnJlcSA9IFRSVUUsICANCiAgICAgeWxpbSA9IGMoMCwgbWF4KGhpc3Rfb2JqJGNvdW50cykgKiAxLjEpKQ0KDQojIG92ZXJsYXkgdGhlIGRlbnNpdHkgcGxvdA0KcGFyKG5ldyA9IFRSVUUpDQpkZW5zaXR5X29iaiA8LSBkZW5zaXR5KGRhdGEkcHJvcF90cmVuZFtkYXRhJHBfdD09MC4xMF0pICAjIGtlcm5lbCBkZW5zaXR5DQoNCnBsb3QoZGVuc2l0eV9vYmosIA0KICAgICBtYWluID0gIiIsIA0KICAgICBjb2wgPSAiYmxhY2siLCANCiAgICAgbHdkID0gMiwgDQogICAgIGF4ZXMgPSBGQUxTRSwgDQogICAgIHhsYWIgPSAiIiwgDQogICAgIHlsYWIgPSAiIiwgDQogICAgIHhsaW0gPSBjKDAsIDEpKQ0KDQpheGlzKDQsIGF0ID0gcHJldHR5KHJhbmdlKGRlbnNpdHlfb2JqJHkpKSkgIA0KbXRleHQoIkRlbnNpdHkiLCBzaWRlID0gNCwgbGluZSA9IDIuNSkgIA0KYGBgIA0KDQoNCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NX0NCmRhdGEkZGlzdCA8LSBmY3RfcmV2KGRhdGEkZGlzdCkNCg0KY3JlYXRlX3Bsb3QgPC0gZnVuY3Rpb24ocF90X2xldmVsKSB7DQogIGRhdGFfZmlsdGVyZWQgPC0gZGF0YSAlPiUgZmlsdGVyKHBfdCA9PSBwX3RfbGV2ZWwpDQogIA0KICBnZ3Bsb3QoZGF0YV9maWx0ZXJlZCwgYWVzKHggPSBhY3R1YWxfcmhvLCB5ID0gYWN0dWFsX3IsIGNvbG9yID0gcHJvcF90cmVuZCkpICsNCiAgICBnZW9tX3BvaW50KHNpemUgPSAxLjUsIGFscGhhID0gMC43KSArDQogICAgZmFjZXRfZ3JpZDIoDQogICAgICByb3dzID0gdmFycyhkaXN0KSwNCiAgICAgIGNvbHMgPSB2YXJzKGFscGhhKSwNCiAgICAgIGxhYmVsbGVyID0gbGFiZWxsZXIoYWxwaGEgPSBmdW5jdGlvbih4KSBwYXN0ZTAoIlx1MDNCMT0iLCB4KSkNCiAgICApICsNCiAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oDQogICAgICBjb2xvcnMgPSBjKCJsaWdodGJsdWUiLCAieWVsbG93IiwgInJlZCIsICJibGFjayIpLA0KICAgICAgdmFsdWVzID0gc2NhbGVzOjpyZXNjYWxlKGMoMCwgMC4yLCAwLjUsIDAuNzUsIDAuOSwgMSkpLA0KICAgICAgbGltaXRzID0gYygwLCAxKSwNCiAgICAgIG5hbWUgPSAiJSBhZ2VudHNcbmZvbGxvd2luZyB0cmVuZCINCiAgICApICsNCiAgICBsYWJzKA0KICAgICAgeCA9IGV4cHJlc3Npb24ocmhvW2t4XSksDQogICAgICB5ID0gZXhwcmVzc2lvbihyW2trXSksDQogICAgICB0aXRsZSA9IHBhc3RlKCJwcm9wb3J0aW9uIHRyZW5kc2V0dGVycyA9IiwgcF90X2xldmVsKQ0KICAgICkgKw0KICAgIHRoZW1lX21pbmltYWwoKSArDQogICAgdGhlbWUoDQogICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAiZ3JleTkwIiwgY29sb3IgPSAiZ3JleTUwIikNCiAgICApDQp9DQoNCiMgZ2VuZXJhdGUgcGxvdHMgZm9yIGVhY2ggbGV2ZWwgb2YgcF90DQpwbG90MSA8LSBjcmVhdGVfcGxvdCh1bmlxdWUoZGF0YSRwX3QpWzFdKQ0KcGxvdDIgPC0gY3JlYXRlX3Bsb3QodW5pcXVlKGRhdGEkcF90KVsyXSkNCnBsb3QzIDwtIGNyZWF0ZV9wbG90KHVuaXF1ZShkYXRhJHBfdClbM10pDQoNCiMgY29tYmluZQ0KY29tYmluZWRfcGxvdCA8LSAocGxvdDEgKyBwbG90MiArIHBsb3QzKSArDQogIHBsb3RfbGF5b3V0KGd1aWRlcyA9ICJjb2xsZWN0IikgJiB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCg0KY29tYmluZWRfcGxvdA0KDQpgYGANCg0KDQpgYGB7cn0NCiMgYSBmdW5jdGlvbiB0byBjcmVhdGUgaGVhdG1hcCBmb3IgYSBzcGVjaWZpYyBsZXZlbCBvZiBwX3QNCmNyZWF0ZV9oZWF0bWFwIDwtIGZ1bmN0aW9uKHBfdF9sZXZlbCkgew0KICAjIGZpbHRlciBkYXRhIGZvciB0aGUgZ2l2ZW4gbGV2ZWwgb2YgcF90DQogIGRhdGFfZmlsdGVyZWQgPC0gZGF0YSAlPiUgZmlsdGVyKHBfdCA9PSBwX3RfbGV2ZWwpDQogIA0KICAjIHN1bW1hcml6ZSB0aGUgZGF0YQ0KICBkYXRhX3N1bW1hcnkgPC0gZGF0YV9maWx0ZXJlZCAlPiUNCiAgICBncm91cF9ieSh0YXJnZXRfcmhvLCB0YXJnZXRfciwgYWxwaGEsIGRpc3QpICU+JQ0KICAgIHN1bW1hcml6ZSgNCiAgICAgIGBQKG5lZy4gY2FzY2FkZSlgID0gbWVhbihwcm9wX3RyZW5kID09IDEsIG5hLnJtID0gVFJVRSksICAjIG1lYW4gb2YgcHJvcF90cmVuZCA9PSAxDQogICAgICBuID0gbigpLCAgIyBjb3VudCBvZiBvYnNlcnZhdGlvbnMgaW4gZWFjaCBncm91cA0KICAgICAgc2VfcHJvYiA9IHNxcnQoKGBQKG5lZy4gY2FzY2FkZSlgICogKDEgLSBgUChuZWcuIGNhc2NhZGUpYCApKSAvIG4pLCAgIyBzdGFuZGFyZCBlcnJvciBmb3IgdGhlIHByb3BvcnRpb24NCiAgICAgIC5ncm91cHMgPSAiZHJvcCINCiAgICApDQogIA0KICAjIGZpbmQgdGhlIGhpZ2hlc3QgdmFsdWUgZm9yIGVhY2ggZmFjZXQNCiAgbWF4X3ZhbHVlcyA8LSBkYXRhX3N1bW1hcnkgJT4lDQogICAgZ3JvdXBfYnkoYWxwaGEsIGRpc3QpICU+JQ0KICAgIGZpbHRlcihgUChuZWcuIGNhc2NhZGUpYCA9PSBtYXgoYFAobmVnLiBjYXNjYWRlKWApKSAlPiUNCiAgICBkaXN0aW5jdChhbHBoYSwgZGlzdCwgLmtlZXBfYWxsID0gVFJVRSkgJT4lDQogICAgdW5ncm91cCgpDQogIA0KICAjIGNyZWF0ZSB0aGUgaGVhdG1hcA0KICBnZ3Bsb3QoZGF0YV9zdW1tYXJ5LCBhZXMoeCA9IHRhcmdldF9yaG8sIHkgPSB0YXJnZXRfciwgZmlsbCA9IGBQKG5lZy4gY2FzY2FkZSlgKSkgKw0KICAgIGdlb21fdGlsZShjb2xvciA9ICJ3aGl0ZSIpICsgICMgIHdpdGggZ3JpZGxpbmVzDQogICAgc2NhbGVfZmlsbF9ncmFkaWVudG4oDQogICAgICBjb2xvcnMgPSBjKCJsaWdodGJsdWUiLCAieWVsbG93IiwgInJlZCIsICJibGFjayIpLCANCiAgICAgIG5hbWUgPSAiUChuZWcuIGNhc2NhZGUpIiwNCiAgICAgIGxpbWl0cyA9IGMoMCwgMSkgICMgZW5zdXJlIHRoZSBmaWxsIHNjYWxlIHJhbmdlcyBiZXR3ZWVuIDAgYW5kIDENCiAgICApICsNCiAgICBmYWNldF9ncmlkMigNCiAgICAgIHJvd3MgPSB2YXJzKGRpc3QpLA0KICAgICAgY29scyA9IHZhcnMoYWxwaGEpLA0KICAgICAgc2NhbGVzID0gImZyZWUiLA0KICAgICAgaW5kZXBlbmRlbnQgPSBUUlVFLA0KICAgICAgbGFiZWxsZXIgPSBsYWJlbGxlcihhbHBoYSA9IGZ1bmN0aW9uKHgpIHBhc3RlMCgiXHUwM0IxPSIsIHgpKSANCiAgICApICsNCiAgICAjIGFkZCBhIGxpbmUgY29ubmVjdGluZyB0aGUgdGV4dCBib3ggdG8gdGhlIGNvcnJlc3BvbmRpbmcgdGlsZSAoZHJhd24gZmlyc3QpDQogICAgI2dlb21fc2VnbWVudChkYXRhID0gbWF4X3ZhbHVlcywgDQogICAgIyAgICAgICAgICAgICBhZXMoeCA9IHRhcmdldF9yaG8sIHkgPSB0YXJnZXRfciwgDQogICAgIyAgICAgICAgICAgICAgICAgeGVuZCA9IHRhcmdldF9yaG8gLSAwLjMsIHllbmQgPSB0YXJnZXRfciksDQogICAgIyAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIsIHNpemUgPSAwLjUpICsgICMgdGhpbiBsaW5lIGNvbm5lY3RpbmcgdGV4dCBhbmQgdGlsZQ0KICAgICMgYWRkIGEgcG9pbnQgYXQgdGhlIGNlbnRlciBvZiB0aGUgY2VsbA0KICAgICNnZW9tX3BvaW50KGRhdGEgPSBtYXhfdmFsdWVzLCANCiAgICAjICAgICAgICAgICBhZXMoeCA9IHRhcmdldF9yaG8sIHkgPSB0YXJnZXRfciksDQogICAgIyAgICAgICAgICAgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMSkgKyAgIyBwb2ludCAoZG90KSBhdCB0aGUgY2VudGVyIG9mIHRoZSBjZWxsDQogICAgIyBhZGQgYSBsYWJlbCBib3ggd2l0aCB0aGUgaGlnaGVzdCB2YWx1ZSBwZXIgcGFuZWwgKGxpZ2h0IGZpbGwgY29sb3IpDQogICAgI2dlb21fbGFiZWwoZGF0YSA9IG1heF92YWx1ZXMsIA0KICAgICMgICAgICAgICAgIGFlcyhsYWJlbCA9IHBhc3RlMCgiUCA9ICIsIHJvdW5kKGBQKG5lZy4gY2FzY2FkZSlgLCAyKSkpLA0KICAgICMgICAgICAgICAgIGZpbGwgPSAiZ3JheTkwIiwgY29sb3IgPSAiYmxhY2siLCBzaXplID0gNCwgDQogICAgIyAgICAgICAgICAgbGFiZWwucGFkZGluZyA9IHVuaXQoMC4yLCAibGluZXMiKSwgbGFiZWwuc2l6ZSA9IDAuMiwgDQogICAgIyAgICAgICAgICAgbnVkZ2VfeCA9IC0wLjMsIG51ZGdlX3kgPSAwKSArICAjIGxpZ2h0IGdyYXkgYmFja2dyb3VuZA0KICAgIGxhYnMoDQogICAgICB4ID0gZXhwcmVzc2lvbihyaG9ba3hdKSwgDQogICAgICB5ID0gZXhwcmVzc2lvbihyW2trXSksIA0KICAgICAgdGl0bGUgPSBwYXN0ZSgiUHJvcG9ydGlvbiB0cmVuZHNldHRlcnMgPSIsIHBfdF9sZXZlbCkgICMgYWRkIGEgdGl0bGUgZm9yIGVhY2ggbGV2ZWwgb2YgcF90DQogICAgKSArDQogICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICB0aGVtZSgNCiAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJncmV5OTAiLCBjb2xvciA9ICJncmV5NTAiKSwNCiAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIgICMgbW92ZSBsZWdlbmQgdG8gdGhlIGJvdHRvbSBmb3IgY29uc2lzdGVudCBsYXlvdXQNCiAgICApDQp9DQoNCiMgZ2VuZXJhdGUgaGVhdG1hcHMgZm9yIGVhY2ggbGV2ZWwgb2YgcF90DQpoZWF0bWFwMSA8LSBjcmVhdGVfaGVhdG1hcCh1bmlxdWUoZGF0YSRwX3QpWzFdKQ0KaGVhdG1hcDIgPC0gY3JlYXRlX2hlYXRtYXAodW5pcXVlKGRhdGEkcF90KVsyXSkNCmhlYXRtYXAzIDwtIGNyZWF0ZV9oZWF0bWFwKHVuaXF1ZShkYXRhJHBfdClbM10pDQoNCiMgYXJyYW5nZSB0aGUgaGVhdG1hcHMgbmV4dCB0byBlYWNoIG90aGVyIHdpdGggYSBzaGFyZWQgbGVnZW5kDQpjb21iaW5lZF9oZWF0bWFwIDwtIChoZWF0bWFwMSArIGhlYXRtYXAyICsgaGVhdG1hcDMpICsNCiAgcGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiKSAmIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0KDQpoZWF0bWFwMQ0KaGVhdG1hcDINCmhlYXRtYXAzDQoNCiNjcmVhdGVfaGVhdG1hcCh1bmlxdWUoZGF0YSRwX3QpWzJdKSArIGxhYnModGl0bGU9TlVMTCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCiNnZ3NhdmUoIi4vZmlndXJlcy9uZWdfY2FzY2FkZV8xMC5wbmciLCBoZWlnaHQ9NSkNCg0KI3A1IDwtIGNyZWF0ZV9oZWF0bWFwKHVuaXF1ZShkYXRhJHBfdClbMV0pDQojcDIwIDwtIGNyZWF0ZV9oZWF0bWFwKHVuaXF1ZShkYXRhJHBfdClbM10pDQoNCiMocDUgKyBwMjApICsNCiMgIHBsb3RfbGF5b3V0KGd1aWRlcyA9ICJjb2xsZWN0IikgJiB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAjImJvdHRvbSIpDQoNCiNnZ3NhdmUoIi4vZmlndXJlcy9uZWdfY2FzY2FkZV81XzIwLnBuZyIsIGhlaWdodD01LCB3aWR0aD04KQ0KDQpgYGANCg0KDQpgYGB7ciwgZmlnLndpZHRoPTE1fQ0KIyBsZXQncyBmaW5kIGEgY2FuZGlkYXRlIG5ldHdva3IgZm9yIG91ciBleHBlcmltZW50Og0KIyBwb3dlci1sYXc7IGFscGhhPTIuNywgcF90PTEwLCByX2trPS0uMywgcmhvX2t4PTAuNg0KDQojIG5ldHdvcmtzIHRoYXQgc2F0c2lmeSB0aGlzOg0KaW5kIDwtIGFzLm51bWVyaWMocm93bmFtZXMoZGF0YVtkYXRhJGRpc3QgPT0gInBvd2VyLWxhdyIgJiBkYXRhJGFscGhhID09IDIuNyAmIGRhdGEkcF90ID09IDAuMTAgJiBkYXRhJHRhcmdldF9yID09IC0wLjMwICYgZGF0YSR0YXJnZXRfcmhvID09IDAuNiAmIGRhdGEkcHJvcF90cmVuZCA9PSAxLF0pKQ0KDQpmb3IgKGkgaW4gc2FtcGxlKDE6bGVuZ3RoKGluZCksNSkpIHsNCiAgZ3JhcGhfcGxvdCA8LSBnZ3Bsb3RpZnk6OmFzLmdyb2IofmZwbG90X2dyYXBoKG5ldHNbW2luZFtpXV1dJG5ldHdvcmspKQ0KICANCiAgYWJtX3Jlc3VsdCA8LSBmYWJtKA0KICAgIG5ldHdvcmsgPSBuZXRzW1tpbmRbaV1dXSRuZXR3b3JrLA0KICAgIHJvdW5kcyA9IDIwLA0KICAgIHBhcmFtcyA9IGxpc3QocyA9IDE1LCBlID0gMTAsIHcgPSA0MCwgeiA9IDUwLCBsYW1iZGExID0gNC4zLCBsYW1iZGEyID0gMS44KSwNCiAgICBwbG90ID0gVFJVRQ0KICApDQogIGFibV9wbG90IDwtIGFzLmdyb2IofnByaW50KGFibV9yZXN1bHQkcGxvdCkpDQoNCiAgZ3JpZC5hcnJhbmdlKGdyYXBoX3Bsb3QsIGFibV9wbG90LCBuY29sID0gMikNCn0NCg0KIyBhbHNvIGEgY29udHJvbCBjb25kaXRpb24NCmluZDIgPC0gYXMubnVtZXJpYyhyb3duYW1lcyhkYXRhW2RhdGEkZGlzdCA9PSAiYmlub21pYWwiICYgZGF0YSRhbHBoYSA9PSAyLjcgJiBkYXRhJHBfdCA9PSAwLjEwICYgZGF0YSR0YXJnZXRfcmhvID09IDAuMSAmIGRhdGEkdGFyZ2V0X3IgPT0gLTAuMDUsXSkpDQoNCiNuZXRzW1tpbmQyW2xlbmd0aChpbmQyKV1dXVtjKCJ0YXJnZXRfciIsICJ0YXJnZXRfcmhvIildDQoNCmZvciAoaSBpbiBzYW1wbGUoMTpsZW5ndGgoaW5kMiksNSkpIHsNCiAgZ3JhcGhfcGxvdCA8LSBnZ3Bsb3RpZnk6OmFzLmdyb2IofmZwbG90X2dyYXBoKG5ldHNbW2luZDJbaV1dXSRuZXR3b3JrKSkNCiAgDQogIGFibV9yZXN1bHQgPC0gZmFibSgNCiAgICBuZXR3b3JrID0gbmV0c1tbaW5kMltpXV1dJG5ldHdvcmssDQogICAgcm91bmRzID0gMjAsDQogICAgcGFyYW1zID0gbGlzdChzID0gMTUsIGUgPSAxMCwgdyA9IDQwLCB6ID0gNTAsIGxhbWJkYTEgPSA0LjMsIGxhbWJkYTIgPSAxLjgpLA0KICAgIHBsb3QgPSBUUlVFDQogICkNCiAgYWJtX3Bsb3QgPC0gYXMuZ3JvYih+cHJpbnQoYWJtX3Jlc3VsdCRwbG90KSkNCg0KICBncmlkLmFycmFuZ2UoZ3JhcGhfcGxvdCwgYWJtX3Bsb3QsIG5jb2wgPSAyKQ0KfQ0KYGBgDQoNCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9OH0NCnBhcihtZnJvdyA9IGMoMiwgMiksIG1hciA9IGMoMiwgMiwgMSwgMSksIG9tYSA9IGMoMCwgMCwgMCwgMCkpDQoNCiNwaWNrIG5ldHdvcmsNCm5ldHdvcmsxIDwtIG5ldHNbW2luZFsxXV1dJG5ldHdvcmsNCg0KI2kgd2FudCB0byBwb3NpdGlvbiB0aGUgbm9kZXMgYWNjb3JkaW5nIHRvIHRoZWlyIHJvbGUNCiNjcmVhdGUgYSBuZXcgbmV0d29yayBvYmplY3QNCm5ldHdvcmtfc2VncmVnYXRlZCA8LSBuZXR3b3JrMQ0KRShuZXR3b3JrX3NlZ3JlZ2F0ZWQpJHdlaWdodCA9IDENCg0KIyBhZGQgZWRnZXMgd2l0aCBoaWdoIHdlaWdodCBiZXR3ZWVuIGFsbCBub2RlcyBpbiB0aGUgc2FtZSBncm91cCANCmZvciAoaSBpbiB1bmlxdWUoVihuZXR3b3JrMSkkcm9sZSkpIHsNCiAgcm9sZVYgPSB3aGljaChWKG5ldHdvcmsxKSRyb2xlID09IGkpDQogIG5ldHdvcmtfc2VncmVnYXRlZCA9IGFkZF9lZGdlcyhuZXR3b3JrX3NlZ3JlZ2F0ZWQsIGNvbWJuKHJvbGVWLCAyKSwgYXR0cj1saXN0KHdlaWdodD0zKSkNCn0NCg0KIyBjcmVhdGUgbGF5b3V0IGJhc2VkIG9uIG5ldHdvcmtfc2VncmVnYXRlZA0Kc2V0LnNlZWQoMTg5KQ0KbGF5b3V0MSA9IGxheW91dF93aXRoX2ZyKG5ldHdvcmtfc2VncmVnYXRlZCkNCg0KIyBzYW1lIGZvciBjb250cm9sIGNvbmRpdGlvbg0KbmV0d29yazIgPC0gbmV0c1tbaW5kMlsxXV1dJG5ldHdvcmsNCm5ldHdvcmtfc2VncmVnYXRlZCA8LSBuZXR3b3JrMg0KRShuZXR3b3JrX3NlZ3JlZ2F0ZWQpJHdlaWdodCA9IDENCg0KIyBhZGQgZWRnZXMgd2l0aCBoaWdoIHdlaWdodCBiZXR3ZWVuIGFsbCBub2RlcyBpbiB0aGUgc2FtZSBncm91cCANCmZvciAoaSBpbiB1bmlxdWUoVihuZXR3b3JrMikkcm9sZSkpIHsNCiAgcm9sZVYgPSB3aGljaChWKG5ldHdvcmsyKSRyb2xlID09IGkpDQogIG5ldHdvcmtfc2VncmVnYXRlZCA9IGFkZF9lZGdlcyhuZXR3b3JrX3NlZ3JlZ2F0ZWQsIGNvbWJuKHJvbGVWLCAyKSwgYXR0cj1saXN0KHdlaWdodD0xKSkNCn0NCg0Kc2V0LnNlZWQoMjg5KQ0KbGF5b3V0MiA9IGxheW91dF93aXRoX2ZyKG5ldHdvcmtfc2VncmVnYXRlZCkNCg0KZnBsb3RfZ3JhcGgobmV0d29yazEsIGxheW91dF9hbGdvID0gbGF5b3V0MSwgbGVnZW5kID0gRkFMU0UsIGNvbDEgPSAicHVycGxlIiwgY29sMiA9ICJvcmFuZ2UiKQ0KbXRleHQoIihBKSIsIHNpZGUgPSAzLCBsaW5lID0gLTEsIGF0ID0gLS41LCBjZXg9MikNCmZwbG90X2dyYXBoKG5ldHdvcmsyLCBsYXlvdXRfYWxnbyA9IGxheW91dDIsIGxlZ2VuZCA9IEZBTFNFLCBjb2wxID0gInB1cnBsZSIsIGNvbDIgPSAib3JhbmdlIikNCm10ZXh0KCIoQikiLCBzaWRlID0gMywgbGluZSA9IC0xLCBhdCA9IC0uNSwgY2V4PTIpDQpmcGxvdF9ncmFwaChuZXR3b3JrMSwgbGF5b3V0X2FsZ28gPSBsYXlvdXRfd2l0aF9mcihuZXR3b3JrMSksIGxlZ2VuZCA9IEZBTFNFLCBjb2wxID0gInB1cnBsZSIsIGNvbDIgPSAib3JhbmdlIikNCmZwbG90X2dyYXBoKG5ldHdvcmsyLCBsYXlvdXRfYWxnbyA9IGxheW91dF93aXRoX2ZyKG5ldHdvcmsyKSwgbGVnZW5kID0gRkFMU0UsIGNvbDEgPSAicHVycGxlIiwgY29sMiA9ICJvcmFuZ2UiKQ0KYGBgIA0KDQotLS0NCg0KIyBSZWZlcmVuY2VzDQoNCg==


Copyright © Rob Franken